回答:
コプロセスはksh
機能です(既にksh88
)。zsh
それだけだけに追加されているが、開始(90年代初頭)から特徴を持っていたbash
中で4.0
(2009年)。
ただし、動作とインターフェイスは3つのシェル間で大きく異なります。
しかし、考え方は同じです。名前付きパイプに頼ることなく、バックグラウンドでジョブを開始し、入力を送信し、出力を読み取ることができます。
一部のシステムでは、ほとんどのシェルとソケットペアのksh93の最新バージョンで、名前のないパイプを使用して行われます。
でa | cmd | b
、a
データをフィードしcmd
、b
その出力を読み取ります。実行中のcmd
共同プロセスとしては、シェルは、両方にすることができますa
とb
。
では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
、コプロセスはとほぼ同じです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
または関数を使って、もっと凝ったことをすることができます。)
コプロセスデータをフィードします。
zpty -w x some data
コプロセスデータの読み取り(最も単純な場合):
zpty -r x var
のようにexpect
、特定のパターンに一致するコプロセスからの出力を待つことができます。
bash構文はかなり新しく、最近ksh93、bash、およびzshに追加された新しい機能の上に構築されます。10を超える動的に割り当てられたファイル記述子を処理できる構文を提供します。
bash
基本的な coproc
構文と拡張された構文を提供します。
コプロセスを開始するための基本的な構文は、次のようになります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]}
ます。
あなたはに複数の共同プロセスを開始することができ、時間ができますが、ない 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にそれらを複製することで0
、1
または2
。これにより、1つのコマンドで対話できるコプロセスの数が制限されます。(例については以下を参照してください。)
yash
本質的にコプロセス機能はありませんが、パイプラインおよびプロセスリダイレクト機能を使用して同じ概念を実装できます。システムコールyash
へのインターフェースを持っているpipe()
ので、この種のことはそこで手で比較的簡単に行うことができます。
あなたはコプロセスを開始します:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
最初にpipe(4,5)
(5の書き込み側、4の読み取り側)を作成し、次にfd 3をパイプにリダイレクトし、もう一方の端でstdinを使用して実行されるプロセスにリダイレクトします。次に、必要のない親のパイプの書き込み側を閉じます。そのため、シェルでfd 3をcmdのstdinに接続し、fd 4を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つのパイプを埋めるだけのデータが送信される場合にのみ。
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の使用例があります。
簡単な例を次に示します。コマンドの出力のコピーを他の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
上記では、コプロセス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 '%s\n' foo bar | f
(注意:システム上で動作しないことをksh
使用するsocketpairs
代わりにpipes
、どこで/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 '%s\n' foo bar | f
あなたは、コマンド、使用と対話したい場合expect
、またはzsh
年代zpty
、または名前付きパイプ。
パイプを使用して派手な配管を行いたい場合は、名前付きパイプを使用します。
コプロセスは上記のいくつかを実行できますが、些細ではないものについては深刻な頭をひっかくように準備してください。
exec {tr[1]}>&-
確かに新しいバージョンで動作するようで、CWRU / changelogエントリで参照されています({array [ind]}のような単語が有効なリダイレクトを許可する... 2012-09-01)。exec {tr[1]}<&-
(または、より適切な>&-
同等のものはclose()
、両方を呼び出すだけなので違いはありません)、coprocのstdinを閉じませんが、そのcoprocへのパイプの書き込み側を閉じます。
yash
。
mkfifo
は、競合状態やパイプアクセスのセキュリティについて心配する必要がないことです。fifoのデッドロックを心配する必要があります。
stdbuf
コマンドは、少なくともそれらのいくつかを防ぐのに役立ちます。Linuxおよびbashで使用しました。とにかく、@StéphaneChazelasが結論に正しいと信じています。名前付きパイプに戻ったときだけ、「頭の引っ掻き」フェーズが終了しました。
コプロセスは、シェルを使用したシェルスクリプト言語で最初に導入され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
「coproc」とは何ですか?
これは、シェルと連携する2番目のプロセスを意味する「コプロセス」の略です。コマンドの最後に「&」で始まるバックグラウンドジョブと非常に似ていますが、親シェルと同じ標準入出力を共有する代わりに、標準I / Oが特別な方法で親シェルに接続されている点が異なります。パイプの種類はFIFO.For参照と呼ばれるここにクリックを
zshでcoprocを開始します
coproc command
このコマンドは、stdinからの読み取りやstdoutへの書き込みを準備する必要があります。そうでない場合、coprocとしてはあまり役に立ちません。
この記事を読んで、ここでそれは、execとcoproc間のケーススタディを提供
|
です。(つまり、ほとんどのシェルでパイプを使用し、ksh93でソケットペアを使用します)。パイプとソケットペアは先入れ先出しで、すべてFIFOです。mkfifo
名前付きパイプを作成し、コプロセスは名前付きパイプを使用しません。
もう1つの良い(そして機能する)例があります-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)
$
bash 4.3.11
ことができます。変数; あなたの答えの例の観点でexec {tr[1]}<&-
、今(coprocの標準入力を閉じるために、あなたのコードは、(間接的に)閉じようとすることに注意して動作します{tr[1]}
使用して>&-
、しかし{tr[1]}
coprocのある標準入力、及びで閉じる必要があります<&-
)。修正4.2.25
は、問題がまだ発生している、と発生し4.3.11
ていないの間にあるはずです。