GNU/Linux >> Linux の 問題 >  >> Linux

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

誰かが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つのコマンドで操作できるコプロセスの数が制限されます。 (例については、以下を参照してください。)

関連:sandbox-execコマンドを使用して信頼できないアプリケーションを安全に実行しますか?

ヤシュプロセスとパイプラインリダイレクト

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を使用します パイプの代わりに 、ここで / 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 、または名前付きパイプ。

パイプを使って派手な配管をしたい場合は、名前付きパイプを使用してください。

共同プロセスは上記のいくつかを実行できますが、重要なことについては深刻な頭を悩ませる準備をしてください。


Linux
  1. Linuxsedコマンドの使用方法

  2. Linuxgrepコマンドの使用方法

  3. Linuxでhistoryコマンドを使用する方法

  1. basenameコマンドの使用方法は?

  2. Linuxでidコマンドを使用する方法

  3. Linux で「screen」コマンドを使用する方法

  1. vmstatコマンドの使用方法

  2. Linuxの履歴コマンドの使用方法

  3. Bash読み取りコマンドの使用方法