コプロセス:-What -How-why?

多くのBashユーザーは、バージョン4のBashに登場するコプロセスの存在を認識しています。 Bashのコプロセスが新しい機能ではなく、1988年にksh88の実装に登場した古代のKornShell機能であることは、わずかに少ない数字でわかっています。 コプロセスの方法を知っているさらに少数のシェルユーザーは、構文を知っており、その方法を覚えています。 おそらく、私は4番目のグループに属します。コプロセスについては知っていて、定期的に使用方法を知っているが、「なぜ?」 「定期的に」と言うのは、時々頭の中で構文を更新するからですが、「これがco-procを使用できる場合」と思う頃には、その方法を完全に忘れています。



このメモでは、さまざまなシェルの構文をまとめて、必要な理由を理解できるように、どうすればよいか覚えていない場合は、少なくともどこに書かれているかを理解できるようにします。



記事のタイトルには3つの質問があります。 順番に行きましょう。



なに?



コプロセスとは何ですか? コプロセッシングとは、2つのプロシージャを同時に実行することで、一方のプロシージャが他方の出力を読み取ります。 これを実装するには、最初にチャネル機能を実行するバックグラウンドプロセスを実行する必要があります。 バックグラウンドプロセスが開始されると、そのstdinstdoutはユーザープロセスに関連付けられたチャネルに割り当てられます。 したがって、書き込み用の1つのチャネル、読み取り用の2番目のチャネル。 これは例で説明する方が簡単なので、2番目の質問に移りましょう。



どうやって?



シェルでのコプロセスの実装はさまざまです。 kshzsh 、およびbashで知っている3つの実装に焦点を当てます。 それらを時系列順に検討してください。 これは記事の問題に直接関連するものではありませんが、以下のすべての例は



$ uname -opr FreeBSD 10.1-STABLE amd64
      
      





Ksh



 $ `echo $0` --version version sh (AT&T Research) 93u+ 2012-08-01
      
      





構文



 cmd |&
      
      





それは私にとって最も論理的なようです。 ここで、 cmdコマンドをバックグラウンドで実行するには、特別な操作|&を使用します。



-「&」-バックグラウンドプロセス。

-「|」 -チャンネル。



バックグラウンドプロセスを開始します。



 $ tr -uab |& [2] 6053
      
      





彼が生きていることを確認してください:



 $ ps afx | grep [6]053 6053 4 IN 0:00.00 tr -uab
      
      





UPD 2016.08.15 21:15
このコンテキストでは、まったく正しいコマンドではありません。 コメントの論理に違反しないように訂正しません。

このようなより正しい:

 $ tr -uab |& [2] 6053 $ ps -p 6053 PID TT STAT TIME COMMAND 6053 4 SN 0:00.00 tr -uab
      
      





ありがとうZyXI



これで、バックグラウンドプロセスと通信できます。 私たちは書きます:



 $ print -p abrakadabra1 $ print -p abrakadabra2 $ print -p abrakadabra3
      
      





そして読む:



 $ read -p var; echo $var bbrbkbdbbrb1 $ read -p var; echo $var bbrbkbdbbrb2 $ read -p var; echo $var bbrbkbdbbrb3
      
      





または:



 $ print abrakadabra1 >&p $ print abrakadabra2 >&p $ print abrakadabra3 >&p $ while read -p var; do echo $var; done bbrbkbdbbrb1 bbrbkbdbbrb2 bbrbkbdbbrb3
      
      





記録のためにパイプの「終了」を閉じます。



 $ exec 3>&p 3>&-
      
      





そして読むために:



 $ exec 3<&p 3<&-
      
      





Zsh



 $ `echo $0` --version zsh 5.2 (amd64-portbld-freebsd10.1)
      
      





zshのコプロセスの構文はkshとあまり変わらないので、驚くことではありません。 彼の男は「zshはkshに最も似ていると言っています。



主な違いは、 |&演算子の代わりにcoprocキーワードを使用することです。 そうでなければ、すべてが非常に似ています:



 $ coproc tr -uab [1] 22810 $ print -p abrakadabra1 $ print abrakadabra2 >&p $ print -p abrakadabra3 $ read -ep bbrbkbdbbrb1 $ while read -p var; do echo $var; done bbrbkbdbbrb2 bbrbkbdbbrb3
      
      





読み取り/書き込みチャネルを閉じるには、 終了イディオムを使用できます。



 $ coproc exit [1] 23240 $ [2] - done tr -uab $ [1] + done exit
      
      





同時に、新しいバックグラウンドプロセスが開始され、すぐに終了しました。 これはkshとは別の違いです。既存のコプロセスを閉じることはできませんが、すぐに新しいコプロセスを開始できます。



 $ coproc tr -uab [1] 24981 $ print -p aaaaa $ read -ep bbbbb $ coproc tr -uad [2] 24982 $ [1] - done tr -uab $ print -p aaaaa $ read -ep ddddd $
      
      





kshでは、次のようになります。



 $ tr -uab |& [1] 25072 $ tr -uad |& ksh93: process already exists
      
      





この機能にもかかわらず、特に"setopt NO_HUP"を使用する場合は、常に明示的にバックグラウンドプロセスを強制終了することをお勧めします。



ここで言及する価値があるのは、出力バッファリングに関連する予期しない結果が得られる場合があることです。そのため、上記の例では-uオプションを指定してtrを使用しています。



 $ man tr | col | grep "\-u" -u Guarantee that any output is unbuffered.
      
      





これはコプロセスのみを指すものではありませんが、例を使用してこの動作を示します。



 $ coproc tr ab [1] 26257 $ print -pa $ read -ep ^C $ [1] + broken pipe tr ab
      
      





バッファがいっぱいではなく、パイプから何も取得しません。 「上に」記入してください:



 $ coproc tr ab [1] 26140 $ for ((a=1; a <= 4096 ; a++)) do print -p 'a'; done $ read -ep b
      
      





もちろん、この動作が私たちに合わない場合は、たとえばstdbufを使用して変更できます



 $ coproc stdbuf -oL -i0 tr ab [1] 30001 $ print -pa $ read -ep b
      
      





バッシュ



 $ `echo $0` --version GNU bash, version 4.3.42(1)-release (amd64-portbld-freebsd10.1)
      
      





bashワードcoprocはbshおよびzshでコプロセスを開始するために使用されますが 、上記のシェルとは異なり、コプロセスは>&pおよび<&pを使用してアクセスされず、 $ COPROC配列を介してアクセスされます。



-レコードの$ {COPROC [0]}

-読むべき$ {COPROC [1]}



したがって、書き込み/読み取り手順は次のようになります。



 $ coproc tr -uab [1] 30131 $ echo abrakadabra1 >&${COPROC[1]} $ echo abrakadabra2 >&${COPROC[1]} $ echo abrakadabra3 >&${COPROC[1]} $ while read -u ${COPROC[0]}; do printf "%s\n" "$REPLY"; done bbrbkbdbbrb1 bbrbkbdbbrb2 bbrbkbdbbrb3
      
      





ハンドルを閉じる:



 $ exec {COPROC[1]}>&- $ cat <&"${COPROC[0]}" [1]+ Done coproc COPROC tr -uab
      
      





何らかの理由でCOPROCという名前が自分に合わない場合は、 独自の名前を指定できます。



 $ coproc MYNAME (tr -uab) [1] 30528 $ echo abrakadabra1 >&${MYNAME[1]} $ read -u ${MYNAME[0]} ; echo $REPLY bbrbkbdbbrb1 $ exec {MYNAME[1]}>&- ; cat <&"${MYNAME[0]}" [1]+ Done coproc MYNAME ( tr -uab )
      
      





なんで?



なぜコプロセスが必要なのかを答える前に、すぐにコプロセスを持たないシェルでそれらの機能を実装できるかどうかを考えます。 たとえば、次の場合:



 $ man sh | col -b | grep -A 4 DESCRIPTION DESCRIPTION The sh utility is the standard command interpreter for the system. The current version of sh is close to the IEEE Std 1003.1 (``POSIX.1'') spec- ification for the shell. It only supports features designated by POSIX, plus a few Berkeley extensions. $ man sh | col -b | grep -A 1 -B 3 AUTHORS This version of sh was rewritten in 1989 under the BSD license after the Bourne shell from AT&T System V Release 4 UNIX. AUTHORS This version of sh was originally written by Kenneth Almquist.
      
      





誰も名前付きパイプをキャンセルしませんでした:



 $ mkfifo in out $ tr -uab <in >out & $ exec 3> in 4< out $ echo abrakadabra1 >&3 $ echo abrakadabra2 >&3 $ echo abrakadabra3 >&3 $ read var <&4 ; echo $var bbrbkbdbbrb1 $ read var <&4 ; echo $var bbrbkbdbbrb2 $ read var <&4 ; echo $var bbrbkbdbbrb3
      
      





おそらく、いくらか誇張して、疑似端末を使用してこれが可能であると言うことができますが、このトピックの開発は始めません。



さて、なぜコプロセスが必要なのですか? Mitch Frazierによる記事の翻訳からの抜粋を引用します。

これまでのところ、コプロセス用の<...>タスクを思いつくことはできません。少なくとも大げさではありません。


実際、私はスクリプトでコプロセスを使用できるのは一度だけでした。 アイデアは、MySQLにアクセスするための「永続的な接続」を実装することでした。

次のようになりました。



 $ coproc stdbuf -oL -i0 mysql -pPASS [1] 19743 $ printf '%s;\n' 'select NOW()' >&${COPROC[1]} $ while read -u ${COPROC[0]}; do printf "%s\n" "$REPLY"; done NOW() 2016-04-06 13:29:57
      
      





それ以外の場合、 coprocを使用しようとするすべての試みは、本当に遠いものでした。



ありがとう
コメント、手紙、メモが記事の執筆に役立ったバートシェーファー、ステファンシャゼラス、ミッチフレイザーに感謝します。



All Articles