web-dev-qa-db-ja.com

相互接続されたコマンド間でデータの循環フローを実装するにはどうすればよいですか?

コマンドを相互に接続する方法は2種類あります。

  1. パイプを使用する(次のコマンドのstd-inputにstd-outputを置く)。
  2. teeを使用して(出力を多数の出力にスプライスします)。

それが可能かどうかわからないので、架空の接続タイプを描画します。

enter image description here

たとえばコマンドの代わりに変数を使用するこの疑似コードのように、コマンド間でデータの循環フローを実装することはどのようにして可能でしょうか。

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}
19
Abdul Al Hazred

_tail -f_で実装された循環I/Oループ

これは循環I/Oループを実装します:

_$ echo 1 >file
$ tail -f file | while read n; do echo $((n+1)); sleep 1; done | tee -a file
2
3
4
5
6
7
[..snip...]
_

これは、あなたが言及した正弦アルゴリズムを使用して循環入力/出力ループを実装します:

_$ echo 1 >file
$ tail -f file | while read n; do echo "1+s(3*$n)" | bc -l; sleep 1; done | tee -a file
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
1.75155632167982146959
[..snip...]
_

ここで、bcは浮動小数点演算を実行し、s(...)は正弦関数のbc表記です。

代わりに変数を使用した同じアルゴリズムの実装

この特定の数学の例では、循環I/Oアプローチは必要ありません。単に変数を更新することができます:

_$ n=1; while true; do n=$(echo "1+s(3*$n)" | bc -l); echo $n; sleep 1; done
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
[..snip...]
_
16
John1024

FIFO、これはmkfifoで作成されたものを使用できます。ただし、veryが誤ってデッドロックを作成しやすいことに注意してください。それについて説明しましょう—架空の「循環」の例を考えてみましょう。コマンドの出力をその入力にフィードします。これがデッドロックする可能性のある方法が少なくとも2つあります。

  1. コマンドには出力バッファーがあります。部分的に埋められていますが、まだフラッシュされていません(実際には書き込まれていません)。いっぱいになったらそうします。したがって、入力の読み取りに戻ります。待機している入力は実際には出力バッファーにあるため、永続的に存在します。そして、その入力を取得するまでフラッシュされません...

  2. コマンドには、書き込む出力がたくさんあります。書き込みを開始しますが、カーネルパイプバッファーがいっぱいになります。だから、彼らはそこに座って、彼らがバッファのスペースになるのを待っています。これは、入力を読み取るとすぐに発生します。つまり、出力への書き込みがすべて完了するまで、そうするつもりはありません。

とはいえ、ここではその方法を示します。この例は、odを使用して、16進ダンプの無限のチェーンを作成します。

mkfifo fifo
( echo "we need enough to make it actually write a line out"; cat fifo ) \ 
    | stdbuf -i0 -o0 -- od -t x1 | tee fifo

最終的に停止することに注意してください。どうして?デッドロック、上記の#2。また、バッファリングを無効にするためのstdbufの呼び出しに気付くかもしれません。それなし?出力のないデッドロック。

12
derobert

一般に、Makefile(コマンドmake)を使用して、ダイアグラムをMakefileルールにマップします。

f1 f2 : f0
      command < f0 > f1 2>f2

反復的/循環的なコマンドを使用するには、反復ポリシーを定義する必要があります。と:

Shell=/bin/bash

a.out : accumulator
    cat accumulator <(date) > a.out
    cp a.out accumulator

accumulator:
    touch accumulator     #initial value

makeは、一度に1つの反復を生成します。

4
JJoao

ダイアグラムが描くように、反復的なフィードバックループが必ずしも必要であるとは私は確信していません-永続的パイプラインコプロセスを使用できるのと同じくらいです。繰り返しになりますが、それほど大きな違いはないかもしれません。コプロセスでラインを開くと、通常のことを何もせずに、情報を書き込んだり読み取ったりするだけで、典型的なスタイルのループを実装できます。

そもそも、bcがあなたにとってコプロセスの第一候補であるように見えます。 bcでは、疑似コードで要求するほとんどのことを実行できるdefine関数を使用できます。たとえば、これを行う非常に単純な関数は次のようになります。

_printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN
_

...印刷されます...

_b=3
c=.14112000805986722210
a=1.14112000805986722210
_

しかし、もちろんlastではありません。 printfのパイプを担当するサブシェルが終了するとすぐprintfa()\nをパイプに書き込んだ直後)パイプは破棄され、 bcの入力が閉じ、それも終了します。それはそれができるほど有用ではありません。

@derobertはすでに言及しています[〜#〜] fifo [〜#〜] s mkfifoユーティリティを使用して名前付きパイプファイルを作成することで得られる。システムカーネルがファイルシステムエントリを両端にリンクすることを除いて、これらも基本的には単なるパイプです。これらは非常に便利ですが、ファイルシステムでパイプが盗まれる危険を冒さずにパイプを使用できるとさらに便利です。

たまたま、あなたのシェルはこれをたくさんします。 プロセス置換を実装するシェルを使用する場合、持続パイプを取得する非常に簡単な手段があり、これを使用してバックグラウンドプロセスに割り当てることができます。通信できる。

たとえば、bashでは、プロセス置換がどのように機能するかを確認できます。

_bash -cx ': <(:)'
+ : /dev/fd/63
_

本当にsubstitutionであることがわかります。シェルは、展開中にlinkへのパスに対応する値をpipeに置き換えます。あなたはそれを利用することができます-そのパイプを使用して、_()_置換自体の中で実行されているプロセスと通信するためだけに制限する必要はありません...

_bash -c '
    eval "exec 3<>"<(:) "4<>"<(:)
    cat  <&4 >&3  &
    echo hey cat >&4
    read hiback  <&3
    echo "$hiback" here'
_

...印刷する...

_hey cat here
_

これで、異なるシェルが異なる方法でcoprocessを実行すること、およびbashに特定の構文が設定されていることを知っています(そしておそらくzshも)-しかし、それらがどのように機能するかわかりません。上記の構文を使用して、bashzshの両方に厳密な規則がなくても事実上同じことを実行できることを知っています-dashと_busybox ash_はhere-documentsで同じ目的を達成しますdashbusyboxはhere-documentsを他の一時ファイルではなくパイプで実行するため2つのdo)

したがって、bcに適用すると...

_eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"
_

...それは難しい部分です。そして、これは楽しい部分です...

_set --
until [ "$#" -eq 10 ]
do    printf '%s()\n' b c a >&"$BCIN"
      set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"
_

...印刷する...

_b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965
_

...そしてそれはまだ実行中です...

_echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"
_

... a()関数を呼び出してインクリメントして出力するのではなく、bcaの最後の値を取得するだけです...

_.29566669586771958965
_

実際には、私がそれを殺してそのIPCパイプを破壊するまで、それは実行し続けます...

_kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT
_
4
mikeserv