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


19

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

  1. パイプを使用して(次のコマンドのstd-outputをstd-inputに入力します)。
  2. Teeを使用して(出力を多数の出力に接合します)。

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

ここに画像の説明を入力してください

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

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}

回答:


16

で実装された円形I / Oループ tail -f

これにより、循環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...]

12

で作成されたFIFOを使用できますmkfifo。ただし、誤ってデッドロックを作成するのは非常に簡単です。それを説明しましょう。仮想の「円形」の例を見てみましょう。コマンドの出力をその入力にフィードします。これにはデッドロックが発生する可能性がある少なくとも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、バッファリングを無効にするために、呼び出しが表示される場合があります。それなしで?出力のないデッドロック。


おかげで、私はこのコンテキストでバッファについて何も知りませんでした、それについてもっと読むためのいくつかのキーワードを知っていますか?
アブドゥルアルハズレッド

1
@AbdulAlHazred入出力のバッファリングについては、stdioバッファリングを検索します。パイプ内のカーネルのバッファーでは、パイプバッファーが機能しているようです。
デロベルト

4

一般に、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つの反復を生成します。


のかわいい乱用ですmakeが、不要:中間ファイルを使用する場合、ループを使用して管理するだけではどうですか?
アレクシス

@alexis、おそらくmakefilesはやり過ぎです。私は、ループについてあまり快適ではありません。クロック、停止状態、または明確な例の概念がありません。最初の図には、ワークフローと関数シグネチャが記憶されていました。複雑なダイアグラムの場合、データ接続またはメイクファイル型のルールが必要になります。(これは単なる虐待的な直観です)
JJoao

@alexis、そしてもちろん、私はあなたに同意します。
JJoao

私は、これは虐待ではないと思います- makeについてです、マクロ、ここで完璧なアプリケーションです。
mikeserv

1
@mikeserv、はい。そして、私たちは皆、悪用するツールがUnixの地下マグナカルタであることを知っています:)
JJoao

4

コプロセス間で永続的なパイプラインを使用できるのと同じくらい、ダイアグラムが描くように、繰り返しフィードバックループが必ずしも必要だとは思いません。繰り返しになりますが、それほど大きな違いはないかもしれません-コプロセスで行を開くと、通常のスタイルループを実装できます。

そもそも、それが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

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

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

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

ではbash、たとえば、あなたがプロセス置換がどのように機能するかを見ることができます:

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

あなたは本当にそれが代替品であることがわかります。シェルは、パイプへのリンクへのパスに対応する値を展開中に置き換えます。あなたはそれを利用することができます- 置換自体の中で実行されるプロセスと通信するためだけにそのパイプを使用するように制約される必要はありません...()

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

...印刷する...

hey cat here

今、私は別のシェルが行うことを知っているコプロセスのさまざまな方法でものを-とにおける特定の構文があるというbashものを設定するための(おそらく1のためzshだけでなく)が -しかし、私はどのようにそれらのものの作品を知りません。私はちょうどあなたが両方でrigmaroleのすべてがなく、実質的に同じことを行うために上記の構文を使用することができることを知っているbashzsh、あなたは非常に同じようなことを行うことができます- dashbusybox ashヒアドキュメントと同じ目的を達成するための理由(dashbusyboxDO here-他の2つのファイルのように、一時ファイルではなくパイプを使用したドキュメント)

したがって、適用されると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"

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

.29566669586771958965

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

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT

1
とても興味深い。最近のbashとzshでは、ファイル記述子を指定する必要がないことに注意してください。たとえばeval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)、同様に動作します
トール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.