さまざまなシェルでcoprocコマンドを使用する方法は?

さまざまなシェルでcoprocコマンドを使用する方法は?

誰かが使用する方法のいくつかの例を提供できますか?coproc

ベストアンサー1

共同処理はksh機能です(すでに開発中ksh88)。この機能は初期(90年代初頭)から存在し、zsh2009年に追加されました。bash4.0

ただし、3つのシェル間の動作とインタフェースには大きな違いがあります。

しかし、アイデアは同じです。つまり、名前付きパイプを使用せずにバックグラウンドで作業を開始し、入力を送信して出力を読み取ることができます。

これは、一部のシステムでは、最新バージョンの ksh93 のほとんどのシェルとソケットのペアに対して名前のないパイプを使用して行われます。

はデータを供給し、a | cmd | b出力を読み取ります。コプロセスで実行するとシェルが 。acmdbcmdab

ksh 共同処理

では、kshセカンダリプロセスを開始します。

cmd |&

cmd以下を実行してデータを提供できます。

echo test >&p

または

print -p test

cmd以下を使用して出力を読み込みます。

read var <&p

または

read -p var

cmdfgバックグラウンドジョブとして実行されたら、、を使用してをbg参照killすることができます。%job-number$!

cmd読み取り中のパイプの書き込み端を閉じるには、次のようにします。

exec 3>&p 3>&-

cmdそして(書き込み中)他のパイプの読み取り端を閉じます。

exec 3<&p 3<&-

パイプファイル記述子を最初に別のfdに保存しないと、2番目のコプロセスを開始する方法はありません。たとえば、

tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p

zsh 共同プロセス

zsh補助プロセスは、のとほぼ同じですksh。唯一の実際の違いは、共同プロセスがキーワードzshcoproc始まるということです。

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ベース)に加えて、疑似ttyベースの構成があります。たとえば、expectほとんどのプログラムと対話するためのkshスタイルの共同プロセスは機能しません。出力がパイプされるとバッファリングを開始します。

ここにいくつかの例があります。

コラボレーションプロセスの開始x:

zmodload zsh/zpty
zpty x cmd

(ここにcmd簡単なコマンドがありますが、evalor関数を使用すると、よりクールな操作を実行できます。)

共同処理のためのフィードデータ:

zpty -w x some data

共同処理されたデータの読み取り(最も簡単な場合):

zpty -r x var

と同様にexpect、与えられたパターンに一致する補助プロセスの特定の出力を待つことができます。

bash 共同プロセス

Bash構文は最近、ksh93、bash、およびzshに追加された新機能に基づいて大幅に更新されました。これは、動的に割り当てられた10以上のファイル記述子を処理するための構文を提供します。

bash供給基本的な coproc文法、そして拡大する一つ。

基本文法

コプロセスを開始する基本的な構文は次のとおりですzsh

coproc cmd

kshまたは からzshコルーチンに入って来るパイプは と>&p経由でアクセスできます<&p

ただし、bashコプロセスのパイプとコプロセスの他のパイプのファイル記述子が配列(および$COPROCそれぞれ)として返されます。したがって...${COPROC[0]}${COPROC[1]}

コプロセスにデータを提供します。

echo xxx >&"${COPROC[1]}"

coprocessからデータを読み込みます。

read var <&"${COPROC[0]}"

基本構文を使用すると、一度に1つの補助プロセスのみを開始できます。

拡張構文

拡張構文では、次のことができます。名前共同プロセス(例:zshzpty共同プロセス):

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実行されたコマンドに渡す唯一の方法はfds0または。これは、単一のコマンドと対話できる共同プロセスの数を制限します。 (下記の例を参照してください。)12

yashプロセスとパイプラインリダイレクト

yash独自の共同処理機能はありませんが、同じ概念を実装するために使用できます。管路そしてプロセスリダイレクト機能。yashシステムコールインターフェイスがあるため、pipe()この種の操作を比較的簡単に手動で実行できます。

次の手順でコラボレーションプロセスを開始できます。

exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-

まず、1つを作成しpipe(4,5)(5書き込み、4読み取り)fd 3をパイプにリダイレクトします。パイプのもう一方の端はstdinとして実行され、stdoutは以前に作成されたパイプに移動します。次に、不要な親パイプでパイプの書き込み端を閉じます。シェルでは、fd 3をcmdのstdinに接続し、fd 4をcmdのstdoutにパイプします。

これらのファイル記述子には close-on-exec フラグは設定されていません。

フィードデータ:

echo data >&3 4<&-

データを読む:

read var <&4 3>&-

いつものようにfdsをオフにすることができます:

exec 3>&- 4<&-

名前付きパイプを使用するよりも利点はほとんどありません。

共同プロセスは、標準の名前付きパイプを介して簡単に実装できます。名前付きパイプがいつ導入されたのかは正確にはわかりませんが、おそらくコプロセスが出現して以来でしkshksh。できます。

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)

その中でデータが流入するところは単一異なるプロセス間の方向。

しかし、共同プロセスと名前付きパイプを使用すると、デッドロックに陥りやすいです。ファイル記述子が開かれないようにしてプロセスをアクティブに保つには、どのコマンドがどのファイル記述子を開いたかを追跡する必要があります。デッドロックの調査は、発生するかどうかが不確実である可能性があるため、難しい場合があります。たとえば、パイプを埋めるのに十分なデータが転送される場合にのみ可能です。

expect効果が思ったより悪いです。

コプロセスの主な目的は、シェルがコマンドと対話する方法を提供することです。しかし、それはうまく動作しません。

上記のデッドロックの最も簡単な形式は次のとおりです。

tr a b |&
echo a >&p
read var<&p

出力が端末に送信されないため、出力はバッファtrリングされます。したがって、ファイルの終わりを確認するstdinか、出力するバッファがいっぱいのデータを蓄積するまで何も出力しません。したがって、上記は、シェルが出力 a\n(2バイトのみ)してからシェルがより多くのデータを送信するのを待つと、無期限にブロックされreadます。tr

簡単に言えば、パイプはコマンドとの対話には適していません。コプロセスは、出力をバッファリングしないコマンドと対話するためにのみ使用できます。またはstdbufたとえば、最近のGNUまたはFreeBSDシステムでは、特定のコマンドを使用してコマンドに出力をバッファリングしないように指示できます。

そのため、expect代わりにzpty擬似端末を使用してください。expectコマンドと対話するように設計されたツールであり、非常にうまく機能します。

ファイル記述子の処理は退屈で正しく実行するのが困難です。

共同処理を使用すると、単純なシェルやチューブよりも複雑な配管作業を完了できます。

その他 Unix.SE 回答coprocの使い方の例があります。

以下は簡単な例です。1つのコマンドの出力コピーを3つの異なるコマンドに供給してから、3つのコマンドの出力を連結する関数が必要だと想像してください。

すべてパイプを使用します。

例: printf '%s\n' foo bar,tr a bsed '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 '%s\n' foo bar | f

上記では、coprocess fdは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 '%s\n' foo bar | f

メモ:kshこれはsocketpairs代わりにを使用するシステムでは機能せず、pipesLinux/dev/fd/nと同様に機能します。 )

上記のkshfdは、2コマンドラインから明示的に渡されない限り、close-on-execフラグで示されています。これがwithのように未使用のファイル記述子を閉じる必要がない理由です。しかし、toと...の新しい値を渡す必要があるzsh理由でもあります。{i1}>&$i1eval$i1teecat

bashclose-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 '%s\n' foo bar | f

結論として

コマンドと対話するには、expectまたはzshzpty名前付きパイプを使用します。

パイプで素晴らしいパイプ作業を行うには、名前付きパイプを使用してください。

コプロセスは上記の作業のいくつかを実行できますが、些細な作業に対して深刻な面倒な作業を行う準備ができています。

おすすめ記事