誰かが coproc
の使用方法の例をいくつか提供できますか?
コプロセスは、ksh
の機能です(ksh88
には既に含まれています)。 zsh
には最初から機能があり(90年代前半)、4.0
(2009)でbash
に追加されたばかりです。
ただし、動作とインターフェイスは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>&-
そして、もう一方のパイプ(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
のように)が、それを複製します。したがって、フィードパイプまたは読み取りパイプを閉じる明示的な方法はありません。その他の開始anothercoproc
。
たとえば、フィードエンドを閉じるには:
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構文はかなり新しく、ksh93、bash、およびzshに最近追加された新機能の上に構築されています。 10を超える動的に割り当てられたファイル記述子の処理を可能にする構文を提供します。
bash
はbasiccoproc
構文とextended構文を提供します。
コプロセスを開始するための基本的な構文は、zsh
's:のようになります。
coproc cmd
ksh
またはzsh
では、コプロセスとの間のパイプは>&p
および<&p
を使用してアクセスされます。
しかし、bash
では、コプロセスからのパイプとコプロセスへのパイプのファイル記述子が$COPROC
配列に返されます(それぞれ${COPROC[0]}
と${COPROC[1]}
。それで…
コプロセスへのデータのフィード:
echo xxx >&"${COPROC[1]}"
コプロセスからデータを読み取ります:
read var <&"${COPROC[0]}"
基本的な構文では、一度に開始できるコプロセスは1つだけです。
拡張構文では、次のことができますnameコプロセス(zsh
zptyコプロセスのように):
coproc mycoproc { cmd; }
コマンドhasは複合コマンドになります。 (上の例がfunction f { ...; }
を連想させることに注意してください。)
今回は、ファイル記述子は${mycoproc[0]}
および${mycoproc[1]}
にあります。
一度に複数のコプロセスを開始できますが、doコプロセスがまだ実行されているときに(非インタラクティブモードであっても)コプロセスを開始すると警告が表示されます。
拡張構文を使用する場合は、ファイル記述子を閉じることができます。
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
と同様に、これらのパイプファイル記述子は、exec-onとしてマークされます。
しかし、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をもう一方の端で実行するプロセスにリダイレクトし、作成されたパイプに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
がコプロセスを考案した後だった可能性があります(おそらく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
でファイルの終わりを検出するか、出力するためにバッファーでいっぱいのデータを蓄積するまで、何も出力しません。したがって、上記では、シェルがa\n
(2バイトのみ)を出力した後、read
がシェルからデータを送信するのを待機しているため、tr
は無期限にブロックされます。
つまり、パイプはコマンドとのやり取りには適していません。コプロセスは、出力をバッファリングしないコマンドと対話するためにのみ使用できますor出力をバッファリングしないように指示できるコマンド。たとえば、最近のGNUまたはFreeBSDシステムでは、一部のコマンドでstdbuf
を使用します。
そのため、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フラグが設定されていますが、notそれらから複製されたものです({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
では、2
の上のfdsは、コマンドラインで明示的に渡されない限り、close-on-execフラグでマークされます。そのため、zsh
のように未使用のファイル記述子を閉じる必要はありませんが、{i1}>&$i1
を実行し、$i1
の新しい値に対してeval
を使用して、tee
およびcat
…に渡す必要があるのもこのためです。
bash
では、exec-onフラグを回避できないため、これを行うことはできません。
上記では、単純な外部コマンドのみを使用しているため、比較的単純です。シェル構造を代わりに使用したい場合、さらに複雑になり、シェルのバグに遭遇し始めます。
上記を、名前付きパイプを使用して同じものと比較します。
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
、または名前付きパイプを使用します。
パイプを使っておしゃれな配管をしたい場合は、名前付きパイプを使用してください。
コプロセスは上記の一部を実行できますが、重要なものを真剣にスクラッチする準備をしてください。
コプロセスは、最初にksh88
シェル(1988)を含むシェルスクリプト言語で導入され、1993年より前のある時点でzsh
で導入されました。
Kshでコプロセスを起動する構文はcommand |&
です。そこから始めて、print -p
を使用してcommand
標準入力に書き込み、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と呼ばれるパイプの一種です。参考までに ここをクリック
1つはzshでcoprocを開始します:
coproc command
コマンドは、stdinからの読み取りやstdoutへの書き込み、あるいはその両方を行う準備ができている必要があります。そうでない場合、coprocとしてはあまり役に立ちません。
この記事を読む ここ execとcoprocの間のケーススタディを提供します
次に、BASHで記述された単純なサーバーであるもう1つの良い(そして機能する)例を示します。 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)
$