たとえば、特定のコマンドを呼び出すと、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
pee
GNU stdbufと次の組み合わせを使用できます。その他のユーティリティ:
echo "Hello world!" | stdbuf -o 1M pee cmd1 cmd2 cmd3 > output
popen(3)
この3つのシェルコマンドラインを入力し、3つすべてをfread
入力するfwrite
と、最大1Mまでバッファリングされます。
アイデアは、少なくとも入力と同じ大きさのバッファを持つことです。これにより、3つのコマンドが同時に開始されても、pee
pclose
3つのコマンドが順番に開始される場合にのみ入力が表示されます。
各実行ごとに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
ただし、zsh
NUL 文字以外のバイナリ入力では、シェルは失敗します。
これは一時ファイルの使用を防ぎますが、入力全体がメモリに保存されることを意味します。
それにもかかわらず、入力をメモリまたは一時ファイルのどこかに保存する必要があります。
これは、タスクを実行するためにいくつかの簡単なツールが協力することを可能にするUnixのアイデアの限界を示しているので、実際には非常に興味深い質問です。
ここでは、作業を完了するためのいくつかのツールがあることを願っています。
- ソースコマンド(ここ
echo
) - スケジューラコマンド(
tee
) - いくつかのフィルタリングコマンド(
cmd1
、、、cmd2
)cmd3
- および集計コマンド(
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
すべての出力は同じ速度で提供されるため、最も遅い出力パイプの速度でのみデータを送信できます。cat
2番目のパイプ(上の画像のパイプライン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に入力がなくなるのを待っているので、空にすることはできません。cmd1
cat
でより多くの入力を待っているので、これ以上入力がないと言う方法はありませんtee
。- そしてブロックされているので、これ以上入力がないと
tee
言う方法はありません。cmd1
依存性サイクルがあるため、デッドロックが発生します。
今解決策は何ですか?より大きなパイプ3と4(すべての出力を含むのに十分な大きさsrc
)がこれを行うことができます。たとえば、との間にデータを挿入して待機しpv -qB 1G
て読み込むと、最大1Gまでデータを保存できます。しかし、これは2つのことを意味します。tee
cmd2/3
pv
cmd2
cmd3
- これは多くのメモリを占め、繰り返すことができます。
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