
誰かが使用する方法のいくつかの例を提供できますか?coproc
?
答え1
共同処理はksh
機能です(すでに開発中ksh88
)。この機能は初期(90年代初頭)から存在し、zsh
2009年に追加されました。bash
4.0
ただし、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>&-
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
。唯一の実際の違いは、共同プロセスがキーワード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ベース)に加えて、疑似ttyベースの構成があります。たとえば、expect
ほとんどのプログラムと対話するためのkshスタイルの共同プロセスは機能しません。出力がパイプされるとバッファリングを開始します。
ここにいくつかの例があります。
コラボレーションプロセスの開始x
:
zmodload zsh/zpty
zpty x cmd
(ここにcmd
簡単なコマンドがありますが、eval
or関数を使用すると、よりクールな操作を実行できます。)
共同処理のためのフィードデータ:
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つの補助プロセスのみを開始できます。
拡張構文
拡張構文では、次のことができます。名前共同プロセス(例: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
実行されたコマンドに渡す唯一の方法はfds0
または。これは、単一のコマンドと対話できる共同プロセスの数を制限します。 (下記の例を参照してください。)1
2
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<&-
さて、なぜそんなに人気がないのですか?
名前付きパイプを使用するよりも利点はほとんどありません。
共同プロセスは、標準の名前付きパイプを介して簡単に実装できます。名前付きパイプがいつ導入されたのかは正確にはわかりませんが、おそらくコプロセスが出現して以来でしksh
たksh
。できます。
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 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 '%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
代わりにを使用するシステムでは機能せず、pipes
Linux/dev/fd/n
と同様に機能します。 )
上記のksh
fdは、2
コマンドラインから明示的に渡されない限り、close-on-execフラグで示されています。これがwithのように未使用のファイル記述子を閉じる必要がない理由です。しかし、toと...の新しい値を渡す必要がある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 '%s\n' foo bar | f
結論として
コマンドと対話するには、expect
またはzsh
のzpty
名前付きパイプを使用します。
パイプで素晴らしいパイプ作業を行うには、名前付きパイプを使用してください。
コプロセスは上記の作業のいくつかを実行できますが、些細な作業に対して深刻な面倒な作業を行う準備ができています。
答え2
ksh88
コプロセスはシェル(1988年)と共にシェルスクリプト言語に最初に導入され、後でzsh
1993年以前に導入されました。
kshで共同プロセスを開始する構文はですcommand |&
。ここでを使用してcommand
標準入力に書き、を使用してprint -p
標準出力を読み取ることができますread -p
。
数十年後、この機能がなかったbashはついにバージョン4.0でこの機能を導入しました。残念ながら、互換性がなく、より複雑な構文が選択されました。
Bash 4.0以降では、次のコマンドを使用してコプロセスを開始できますcoproc
。たとえば、次のようになります。
$ coproc awk '{print $2;fflush();}'
その後、次のようにしてstdinコマンドに何かを渡すことができます。
$ echo one two three >&${COPROC[1]}
そして、次のように awk 出力を読み込みます。
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
kshでは、次のようになります。
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
答え3
ここに別の良い(そして動作する)例があります。それはBASHで書かれたシンプルなサーバーです。 OpenBSDが必要で、netcat
クラシックは機能しません。もちろん、Unixソケットの代わりにinetソケットを使用することもできます。
server.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
PIDFILE=server.pid
(
exec </dev/null
exec >/dev/null
exec 2>/dev/null
coproc SERVER {
exec nc -l -k -U $SOCKET
}
echo $SERVER_PID > $PIDFILE
{
while read ; do
echo "pong $REPLY"
done
} <&${SERVER[0]} >&${SERVER[1]}
rm -f $PIDFILE
rm -f $SOCKET
) &
disown $!
client.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
coproc CLIENT {
exec nc -U $SOCKET
}
{
echo "$@"
read
} <&${CLIENT[0]} >&${CLIENT[1]}
echo $REPLY
使用法:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
答え4
「コルーチン」とは何ですか?
「co-process」の略で、シェルと協力する第2のプロセスを意味します。標準I / Oが同じ標準入力と出力を共有するのではなく、特殊I / Oを介して親シェルに接続されていることを除いて、コマンドの最後に「&」で始まるバックグラウンド操作と非常によく似ています。親シェルです。 FIFOと呼ばれるパイプの一種です。参考までにここをクリック
zshでcoprocを起動します。
coproc command
コマンドは、stdinから読み取るかstdoutに書き込む準備ができている必要があります。それ以外の場合は、コプロックとしてあまり使用されません。
この記事を読んでくださいここexecとcoprocの間のケーススタディを提供します。