tee + cat:出力を複数回使用し、結果を連結します。

tee + cat:出力を複数回使用し、結果を連結します。

たとえば、特定のコマンドを呼び出すと、echoそのコマンドの結果を他の複数のコマンドに使用できますtee。例:

echo "Hello world!" | tee >(command1) >(command2) >(command3)

catを使用すると、複数のコマンドの結果を収集できます。例:

cat <(command1) <(command2) <(command3)

私は2つのことを同時に実行して、他のものtee(たとえば、私が書いたもの)の出力からこれらのコマンドを呼び出して、すべてのecho結果を単一の出力として収集できるようにしたいと思いますcat

command1結果を順番に維持することが重要です。つまり、、command2および出力の行はcommand3インターリーブされてはならず、コマンドに従ってソートされなければなりません(発生した場合cat)。

catおそらく、そしてより良いオプションがあるかもしれませんが、teeこれは私が今まで知っているオプションです。

入出力サイズが大きくなる可能性があるため、一時ファイルの使用を避けたい。

どうすればいいですか?

PD:もう一つの問題は、これがループで発生し、一時ファイルを処理するのがより難しいことです。これは私の現在のコードで、小さなテストケースではうまく機能しますが、私が理解できない方法でauxfileで読み書きするときに無限ループが生成されます。

somefunction()
{
  if [ $1 -eq 1 ]
  then
    echo "Hello world!"
  else
    somefunction $(( $1 - 1 )) > auxfile
    cat <(command1 < auxfile) \
        <(command2 < auxfile) \
        <(command3 < auxfile)
  fi
}

auxfile 読み取りと書き込みが重なって、すべてが爆発するようです。

ベストアンサー1

peeGNU stdbufと次の組み合わせを使用できます。その他のユーティリティ:

echo "Hello world!" | stdbuf -o 1M pee cmd1 cmd2 cmd3 > output

popen(3)この3つのシェルコマンドラインを入力し、3つすべてをfread入力するfwriteと、最大1Mまでバッファリングされます。

アイデアは、少なくとも入力と同じ大きさのバッファを持つことです。これにより、3つのコマンドが同時に開始されても、pee pclose3つのコマンドが順番に開始される場合にのみ入力が表示されます。

各実行ごとにpcloseバッファpeeをコマンドでフラッシュし、終了するまで待ちます。cmdxこれは、これらのコマンドが入力を受け取る前に何も出力を開始しない限り(そして上位戻り後に出力を続行できるプロセスをフォークしない限り)、これら3つのコマンドの出力がインターリーブされないことを保証します。

実際、これはメモリ内の一時ファイルを使用するのと少し似ていますが、これら3つのコマンドが同時に実行されるという欠点があります。

pee同時にコマンドを開始したくない場合は、シェル関数で作成できます。

pee() (
  input=$(cat; echo .)
  for i do
    printf %s "${input%.}" | eval "$i"
  done
)
echo "Hello world!" | pee cmd1 cmd2 cmd3 > out

ただし、zshNUL 文字以外のバイナリ入力では、シェルは失敗します。

これは一時ファイルの使用を防ぎますが、入力全体がメモリに保存されることを意味します。

それにもかかわらず、入力をメモリまたは一時ファイルのどこかに保存する必要があります。

これは、タスクを実行するためにいくつかの簡単なツールが協力することを可能にするUnixのアイデアの限界を示しているので、実際には非常に興味深い質問です。

ここでは、作業を完了するためのいくつかのツールがあることを願っています。

  • ソースコマンド(ここecho
  • スケジューラコマンド( tee)
  • いくつかのフィルタリングコマンド(cmd1、、、cmd2cmd3
  • および集計コマンド(cat)。

同時に実行して処理したいデータが出たらすぐに処理できるといいでしょう。

フィルタコマンドの場合は簡単です。

src | tee | cmd1 | cat

すべてのコマンドは同時に実行され、cmd1データが利用可能になるとすぐにデータを噛み始めます。src

これで、3つのフィルタコマンドを使用して同じことができます。つまり、すべて同時に開始してパイプすることです。

               ┏━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━┓
               ┃   ┃░░░░2░░░░░┃cmd1┃░░░░░5░░░░┃   ┃
               ┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃
┏━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃░░░░1░░░░░┃tee┃░░░░3░░░░░┃cmd2┃░░░░░6░░░░┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔┗━━━┛
               ┃   ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃
               ┃   ┃░░░░4░░░░░┃cmd3┃░░░░░7░░░░┃   ┃
               ┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛

私たちはこれを比較的簡単にすることができます名前付きパイプ:

pee() (
  mkfifo tee-cmd1 tee-cmd2 tee-cmd3 cmd1-cat cmd2-cat cmd3-cat
  { tee tee-cmd1 tee-cmd2 tee-cmd3 > /dev/null <&3 3<&- & } 3<&0
  eval "$1 < tee-cmd1 1<> cmd1-cat &"
  eval "$2 < tee-cmd2 1<> cmd2-cat &"
  eval "$3 < tee-cmd3 1<> cmd3-cat &"
  exec cat cmd1-cat cmd2-cat cmd3-cat
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'

} 3<&0(上記は、リダイレクトで反対側の端()も開くまでパイプブロックが開かれるのを防ぐために使用するという事実を説明するためのものです。)&stdin/dev/null<>cat

あるいは、名前付きパイプを避けるには、coprocを使用するのがより難しくなりますzsh

pee() (
  n=0 ci= co= is=() os=()
  for cmd do
    eval "coproc $cmd $ci $co"

    exec {i}<&p {o}>&p
    is+=($i) os+=($o)
    eval i$n=$i o$n=$o
    ci+=" {i$n}<&-" co+=" {o$n}>&-"
    ((n++))
  done
  coproc :
  read -p
  eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'

今の問題は、すべてが準備され接続された後、データが流れ続けるかどうかです。

2つの制限があります。

  • teeすべての出力は同じ速度で提供されるため、最も遅い出力パイプの速度でのみデータを送信できます。
  • cat2番目のパイプ(上の画像のパイプライン6)からの読み取りは、最初のパイプ(5)からすべてのデータを読み取った後にのみ開始されます。

これはcmd1、パイプ6が完了するまでデータがパイプ6に流れないことを意味します。そして、上記のシナリオと同様に、tr b Bこれはパイプ3にもデータが流れないことを意味できます。これは、データがtee最も多数のパイプに流れるため、パイプ2、3、4のいずれにもデータが流れないことを意味する。 3つのパイプすべてにパイプが含まれています。転送速度が遅い。

実際、これらのパイプのサイズはnullではないため、一部のデータが通過する可能性があります。少なくとも私のシステムでは、次のように動作させることができます。

yes abc | head -c $((2 * 65536 + 8192)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c -c

さらに、

yes abc | head -c $((2 * 65536 + 8192 + 1)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c

私たちはデッドロックに達し、状況は次のとおりです。

               ┏━━━┓▁▁▁▁2▁▁▁▁▁┏━━━━┓▁▁▁▁▁5▁▁▁▁┏━━━┓
               ┃   ┃░░░░░░░░░░┃cmd1┃░░░░░░░░░░┃   ┃
               ┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃
┏━━━┓▁▁▁▁1▁▁▁▁▁┃   ┃▁▁▁▁3▁▁▁▁▁┏━━━━┓▁▁▁▁▁6▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃██████████┃tee┃██████████┃cmd2┃██████████┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔┗━━━┛
               ┃   ┃▁▁▁▁4▁▁▁▁▁┏━━━━┓▁▁▁▁▁7▁▁▁▁┃   ┃
               ┃   ┃██████████┃cmd3┃██████████┃   ┃
               ┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛

パイプ3とパイプ6(それぞれ64kiB)を満たしました。tee対応する余分なバイトを読み、それを供給しましcmd1たが、

  • cmd2これで、パイプ3への書き込みが削除を待っているためブロックされました。
  • cmd2パイプ6への書き込みがブロックされ、フラッシュできません。catフラッシュを待っています。
  • catパイプ5に入力がなくなるのを待っているので、空にすることはできません。
  • cmd1catでより多くの入力を待っているので、これ以上入力がないと言う方法はありませんtee
  • そしてブロックされているので、これ以上入力がないとtee言う方法はありません。cmd1

依存性サイクルがあるため、デッドロックが発生します。

今解決策は何ですか?より大きなパイプ3と4(すべての出力を含むのに十分な大きさsrc)がこれを行うことができます。たとえば、との間にデータを挿入して待機しpv -qB 1Gて読み込むと、最大1Gまでデータを保存できます。しかし、これは2つのことを意味します。teecmd2/3pvcmd2cmd3

  1. これは多くのメモリを占め、繰り返すことができます。
  2. cmd2データ処理は実際にはcmd1が完了した後にのみ開始されるため、3つのコマンドがすべて一緒に機能することはできません。

2番目の問題の解決策は、パイプ6と7も大きくすることです。消費するだけの出力を想定してcmd2生成すると、メモリは消費されなくなります。cmd3

最初の質問で冗長データを回避する唯一の方法は、スケジューラ自体にデータ保存を実装することです。つまり、tee最速の出力レートでデータを提供できるバリエーションを実装することです(スケジューラに供給するデータを保存)。スピードが遅くなります。)実際にはマイナーではありません。

したがって、最終的にプログラミングなしで合理的に達成できる最善の方法は、おそらく次のようになります(Zsh構文)。

max_hold=1G
pee() (
  n=0 ci= co= is=() os=()
  for cmd do
    if ((n)); then
      eval "coproc pv -qB $max_hold $ci $co | $cmd $ci $co | pv -qB $max_hold $ci $co"
    else
      eval "coproc $cmd $ci $co"
    fi

    exec {i}<&p {o}>&p
    is+=($i) os+=($o)
    eval i$n=$i o$n=$o
    ci+=" {i$n}<&-" co+=" {o$n}>&-"
    ((n++))
  done
  coproc :
  read -p
  eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
yes abc | head -n 1000000 | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c

おすすめ記事