誰かがcoproc
の使用方法に関するいくつかの例を提供できますか ?
承認された回答:
コプロセスはksh
です 機能(すでに ksh88
にあります )。 zsh
この機能は最初(90年代初頭)からありましたが、 bash
に追加されたばかりです。 4.0
で (2009)。
ただし、動作とインターフェイスは3つのシェル間で大幅に異なります。
ただし、考え方は同じです。バックグラウンドでジョブを開始し、名前付きパイプに頼ることなく、入力を送信して出力を読み取ることができます。
これは、ほとんどのシェルを備えた無名パイプと、一部のシステムでは最近のバージョンのksh93を備えたソケットペアで行われます。
a | cmd | b
、 a
データをcmd
にフィードします およびb
その出力を読み取ります。 cmd
を実行しています コプロセスとして、シェルを a
の両方にすることができます およびb
。
kshコプロセス
ksh
で 、次のようにコプロセスを開始します:
cmd |&
cmd
にデータをフィードします 次のようなことを行うことによって:
echo test >&p
または
print -p test
そして、 cmd
を読んでください の出力は次のようになります:
read var <&p
または
read -p var
cmd
バックグラウンドジョブとして開始されます。fg
を使用できます 、 bg
、 kill
その上で%job-number
で参照してください または$!
経由 。
パイプの書き込み端を閉じるにはcmd
から読んでいる、あなたがすることができます:
exec 3>&p 3>&-
そして、もう一方のパイプ(1つの cmd
)の読み取り端を閉じます に書き込みます):
exec 3<&p 3<&-
最初にパイプファイル記述子を他のfdsに保存しない限り、2番目のコプロセスを開始することはできません。例:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
zshコプロセス
zsh
で 、コプロセスは ksh
のものとほぼ同じです 。唯一の本当の違いは、 zsh
コプロセスはcoproc
で開始されます キーワード。
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
実行中:
exec 3>&p
注:これは coproc
を移動しません fd 3
へのファイル記述子 ( ksh
のように )、しかしそれを複製します。したがって、供給パイプまたは読み取りパイプを閉じる明示的な方法はありません。他の方法では、別のを開始します。 coproc
。
たとえば、フィードエンドを閉じるには:
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
パイプベースのコプロセスに加えて、 zsh
(2000年にリリースされた3.1.6-dev19以降) expect
のような疑似ttyベースの構造があります 。ほとんどのプログラムと対話するために、プログラムは出力がパイプであるときにバッファリングを開始するため、kshスタイルのコプロセスは機能しません。
ここにいくつかの例があります。
コプロセスを開始しますx
:
zmodload zsh/zpty
zpty x cmd
(ここでは、 cmd
簡単なコマンドです。ただし、 eval
を使用すると、より洗練された処理を実行できます。 または機能します。)
コプロセスデータをフィードする:
zpty -w x some data
コプロセスデータの読み取り(最も単純な場合):
zpty -r x var
expect
のように 、指定されたパターンに一致するコプロセスからの出力を待つことができます。
bashコプロセス
bash構文ははるかに新しく、ksh93、bash、およびzshに最近追加された新機能の上に構築されています。 10を超える動的に割り当てられたファイル記述子を処理できるようにする構文を提供します。
bash
基本を提供します coproc
構文、および拡張 1つ。
基本構文
コプロセスを開始するための基本的な構文は、 zsh
のようになります。 の:
coproc cmd
ksh
で またはzsh
、コプロセスとの間のパイプには、>&p
を使用してアクセスします。 および<&p
。
しかし、 bash
では 、コプロセスからのパイプとコプロセスへの他のパイプのファイル記述子が $ COPROC
に返されます。 配列(それぞれ $ {COPROC [0]}
および${COPROC [1]}
。だから…
コプロセスにデータをフィードする:
echo xxx >&"${COPROC[1]}"
コプロセスからデータを読み取る:
read var <&"${COPROC[0]}"
基本的な構文では、一度に1つのコプロセスのみを開始できます。
拡張構文
拡張構文では、名前を使用できます。 コプロセス( zsh
のように) zpty共同プロセス):
coproc mycoproc { cmd; }
コマンドはあります 複合コマンドになります。 (上記の例が function f {...;}
を彷彿とさせることに注意してください。 。)
今回は、ファイル記述子は $ {mycoproc [0]}
にあります および${mycoproc [1]}
。
一度に複数のコプロセスを開始できますが、実行 コプロセスの実行中にコプロセスを開始すると(非対話型モードでも)、警告が表示されます。
拡張構文を使用する場合は、ファイル記述子を閉じることができます。
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
その方法で閉じることは、代わりにそれを書かなければならない4.3より前のbashバージョンでは機能しないことに注意してください:
fd=${tr[1]}
exec {fd}>&-
ksh
のように およびzsh
、これらのパイプファイル記述子はclose-on-execとしてマークされています。
しかし、 bash
では 、実行されたコマンドにそれらを渡す唯一の方法は、それらをfds に複製することです。 、
1
、または 2
。これにより、1つのコマンドで操作できるコプロセスの数が制限されます。 (例については、以下を参照してください。)
ヤシュプロセスとパイプラインリダイレクト
yash
それ自体にはコプロセス機能はありませんが、同じ概念をパイプラインで実装できます。 およびプロセス リダイレクト機能。 yash
pipe()
へのインターフェースがあります システムコールなので、この種のことは手作業で比較的簡単に行うことができます。
共同プロセスを開始するのは次のとおりです:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
これは最初にpipe(4,5)
を作成します (5書き込み側、4読み取り側)次に、fd 3をパイプにリダイレクトし、もう一方の端でstdinを使用して実行されるプロセスに転送し、stdoutを前に作成したパイプに転送します。次に、必要のない親のパイプの書き込み端を閉じます。これで、シェルでfd 3がcmdのstdinに接続され、fd4がcmdのstdoutにパイプで接続されました。
これらのファイル記述子には、close-on-execフラグが設定されていないことに注意してください。
データをフィードするには:
echo data >&3 4<&-
データを読み取るには:
read var <&4 3>&-
そして、通常どおりfdsを閉じることができます:
exec 3>&- 4<&-
さて、なぜそれほど人気がないのか
名前付きパイプを使用するよりもほとんどメリットがありません
コプロセスは、標準の名前付きパイプを使用して簡単に実装できます。正確な名前付きパイプがいつ導入されたかはわかりませんが、 ksh
の後であった可能性があります コプロセスを思いついた(おそらく80年代半ばに、ksh88は88年に「リリース」されたが、 ksh
その数年前にAT&Tで社内で使用されていたため、その理由が説明されます。
cmd |&
echo data >&p
read var <&p
次のように書くことができます:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
特に複数のコプロセスを実行する必要がある場合は、これらとの対話がより簡単になります。 (以下の例を参照してください。)
coproc
を使用する唯一の利点 使用後にこれらの名前付きパイプをクリーンアップする必要がないということです。
デッドロックが発生しやすい
シェルはいくつかの構成でパイプを使用します:
- シェルパイプ:
cmd1 | cmd2
、 - コマンド置換:
$(cmd)
、 - およびプロセス置換:
<(cmd)
、>(cmd)
。
それらの中で、データは1つだけで流れます 異なるプロセス間の方向。
ただし、コプロセスと名前付きパイプを使用すると、デッドロックに陥りやすくなります。どのコマンドがどのファイル記述子を開いているかを追跡して、1つのコマンドが開いたままになり、プロセスが存続するのを防ぐ必要があります。デッドロックは非決定論的に発生する可能性があるため、調査が難しい場合があります。たとえば、1つのパイプを埋めるだけのデータが送信された場合のみ。
はexpect
よりも動作が悪い 設計された目的のために
コプロセスの主な目的は、コマンドと対話する方法をシェルに提供することでした。ただし、あまりうまく機能しません。
上記のデッドロックの最も単純な形式は次のとおりです。
tr a b |&
echo a >&p
read var<&p
その出力は端末に送信されないため、 tr
その出力をバッファリングします。そのため、 stdin
にファイルの終わりが表示されるまで何も出力されません。 、または出力するデータでいっぱいのバッファを蓄積しました。したがって、上記では、シェルが an
を出力した後 (2バイトのみ)、 read
tr
のため、無期限にブロックされます シェルがさらにデータを送信するのを待っています。
つまり、パイプはコマンドの操作には適していません。コプロセスは、出力をバッファリングしないコマンドと対話するためにのみ使用できます。または 出力をバッファリングしないように指示できるコマンド。たとえば、 stdbuf
を使用します 最近のGNUまたはFreeBSDシステムでいくつかのコマンドを使用します。
それがexpect
の理由です またはzpty
代わりに疑似端末を使用してください。 expect
はコマンドと対話するために設計されたツールであり、それはうまく機能します。
ファイル記述子の処理は面倒で、正しく理解するのは困難です
共同プロセスを使用して、単純なシェルパイプで許可されているよりも複雑な配管を行うことができます。
他のUnix.SEの回答には、coprocの使用例があります。
簡単な例を次に示します: コマンドの出力のコピーを他の3つのコマンドにフィードし、それらの3つのコマンドの出力を連結する関数が必要だとします。
すべてパイプを使用しています。
例: printf'%sn' foo bar
の出力をフィードします tr a b
へ 、 sed's /./&&/ g'
、および cut -b2-
次のようなものを取得するには:
foo
bbr
ffoooo
bbaarr
oo
ar
まず、それは必ずしも明白ではありませんが、そこでデッドロックが発生する可能性があり、わずか数キロバイトのデータの後で発生し始めます。
次に、シェルに応じて、さまざまな方法で対処する必要のあるさまざまな問題が発生します。
関連:ワイルドカード*はコマンドとしてどのように解釈されますか?
たとえば、 zsh
、あなたはそれを行うでしょう:
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%sn' foo bar | f
上記では、コプロセスfdsにclose-on-execフラグが設定されていますが、ではありません。 それらから複製されたもの( {o1} <&p
のように) )。したがって、デッドロックを回避するには、デッドロックを必要としないプロセスでデッドロックが閉じられていることを確認する必要があります。
同様に、サブシェルを使用して exec cat
を使用する必要があります 結局、パイプを開いたままにすることについてシェルプロセスがないことを確認するためです。
ksh
を使用 (ここでは ksh93
)、それは次のようにする必要があります:
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%sn' foo bar | f
(注: ksh
のシステムでは機能しません socketpairs
を使用します パイプcode>の代わりに 、ここで
/ dev / fd / n
Linuxのように動作します。)
ksh
で 、 2
より上のfds コマンドラインで明示的に渡されない限り、close-on-execフラグでマークされます。そのため、 zsh
のように未使用のファイル記述子を閉じる必要はありません。 —しかし、それは私たちが {i1}>&$ i1
をしなければならない理由でもあります eval
を使用します $ i1
の新しい値 、 tee
に渡されます およびcat
…
bash
で close-on-execフラグを回避できないため、これを行うことはできません。
上記では、単純な外部コマンドのみを使用しているため、比較的単純です。代わりにシェル構造を使用したい場合はさらに複雑になり、シェルのバグに遭遇し始めます。
上記を名前付きパイプを使用した同じものと比較してください:
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%sn' foo bar | f
結論
コマンドを操作する場合は、 expected
を使用します 、または zsh
のzpty
、または名前付きパイプ。
パイプを使って派手な配管をしたい場合は、名前付きパイプを使用してください。
共同プロセスは上記のいくつかを実行できますが、重要なことについては深刻な頭を悩ませる準備をしてください。