Linuxでは、パイプを実行することは可能ですか?
_cmd1 | cmd2
_
そのような方法で:
_cmd2
_は、_cmd1
_が完全に終了するまで実行を開始せず、
_cmd1
_にエラーがある場合、_cmd2
_はまったく実行されず、パイプの終了ステータスは_cmd1
_の終了ステータスになります。
例を挙げると、このパイプの作り方は次のとおりです。
_false | echo ok
_
何も出力せず、ゼロ以外のステータスを返しますか?
_set -o pipefail
_
パイプにはゼロ以外の終了ステータスがありますが、_cmd2
_が失敗しても、_cmd1
_は引き続き実行されます。
_cmd1 && cmd2
_
これはパイプではありません。 I/Oリダイレクトはありません。
_mkfifo /tmp/fifo
cmd1 > /tmp/fifo && cmd2 < /tmp/fifo
_
ブロックします。
_touch /tmp/file
cmd1 > /tmp/file && cmd2 < /tmp/file
_
これは機能しているようです。しかし、いくつかの欠点があります。
I/Oが遅いディスクにデータを書き込みます。 (確かにtmpfsを使用できますが、それは追加のシステム要件です)。
一時ファイル名は慎重に選択する必要があります。そうしないと、既存のファイルが誤って上書きされる可能性があります。 mktempが役立つ場合がありますが、名前のないパイプを使用すると、命名の手間を完全に節約できます。
一時ファイルが存在するファイルシステムは、データ全体を保持するのに十分な大きさではない可能性があります。
一時ファイルは自動クリーンアップされません。
_cmd1
_の出力のサイズはわかりませんが、パイプ 制限されたバッファサイズ です。その量のデータがパイプに書き込まれると、それ以降の書き込みは、誰かがパイプを読み取るまでブロックされます(失敗したソリューション3の種類)。
ブロックしないことを保証するメカニズムを使用する必要があります。非常に大きなデータの場合は、一時ファイルを使用します。それ以外の場合、データをメモリに保持する余裕がある場合(結局のところ、パイプを使用したアイデアでした)、次のように使用します。
_result=$(cmd1) && cmd2 < <(printf '%s' "$result")
unset result
_
ここで、_cmd1
_の結果は変数result
に格納されます。 _cmd1
_が成功すると、_cmd2
_が実行され、result
のデータが提供されます。最後に、result
が設定解除され、関連するメモリが解放されます。
注:以前は、ヒア文字列(_<<< "$result"
_)を使用して_cmd2
_にデータを供給していましたが、StéphaneChazelasがbash
が一時ファイルを作成することを確認しました。 。
コメントの質問への回答:
はい、コマンドは連鎖させることができますアドリブ:
_result=$(cmd1) \
&& result=$(cmd2 < <(printf '%s' "$result")) \
&& result=$(cmd3 < <(printf '%s' "$result")) \
...
&& cmdN < <(printf '%s' "$result")
unset result
_
いいえ、上記のソリューションは次の理由でバイナリデータには適していません。
$(...)
は、末尾の改行をすべて削除します。\0
_)の動作は指定されていません(たとえば、Bashはそれらを破棄します)。はい、バイナリデータに関するこれらすべての問題を回避するには、_base64
_(またはuuencode
、またはNUL文字と末尾の改行のみを処理する自家製のエンコーダ)のようなエンコーダーを使用できます。
_result=$(cmd1 > >(base64)) && cmd2 < <(printf '%s' "$result" | base64 -d)
unset result
_
ここでは、_cmd1
_の終了値をそのまま維持するために、プロセス置換(>(...)
)を使用する必要がありました。
とはいえ、データがディスクに書き込まれないようにするためだけに、これもかなり面倒なようです。中間の一時ファイルがより良い解決策です。 ステファンの答え を参照してください。これは、それに関するほとんどの懸念に対処します。
パイプコマンドの要点は、コマンドを同時に実行して、一方が他方の出力を読み取ることです。それらを順番に実行する場合、および配管のメタファーを保持する場合は、最初のコマンドの出力をバケットにパイプして(保存して)、バケットを空にして他のコマンドに入れる必要があります。
ただし、パイプでそれを行うということは、最初のコマンド用に2つのプロセス(コマンドとパイプのもう一方の端から出力を読み取ってバケットに格納する別のプロセス)と、2番目のコマンド用に2つ(1つはバケットを一方の端に空にする)を持つことを意味しますコマンドがもう一方の端からそれを読み取るためのパイプの)。
バケットには、メモリまたはファイルシステムのいずれかが必要です。メモリは適切に拡張できないため、パイプが必要です。ファイルシステムはもっと理にかなっています。それが/tmp
はです。一時ファイルが削除されてからかなり後になるまでデータがフラッシュされない可能性があるため、ディスクにはデータが表示されない可能性が高いことに注意してください。そうでなければ、そもそもデータが大きすぎてメモリに収まらなかったはずです。
一時ファイルは常にシェルで使用されることに注意してください。ほとんどのシェルでは、ヒアドキュメントとヒア文字列は一時ファイルで実装されています。
に:
cat << EOF
foo
EOF
ほとんどのシェルは一時ファイルを作成し、書き込みと読み取りのために開き、削除し、foo
で埋めてから、読み取り用に開いたfdから複製されたstdinを使用してcat
を実行します。ファイルは、いっぱいになる前に削除されます(これにより、書き込まれた内容が停電に耐える必要がないという手がかりをシステムに与えます)。
あなたはここで同じことをすることができます:
tmp=$(mktemp) && {
rm -f -- "$tmp" &&
cmd1 >&3 3>&- 4<&- &&
cmd2 <&4 4<&- 3>&-
} 3> "$tmp" 4< "$tmp"
そうすれば、ファイルが最初から削除されるので、クリーンアップについて心配する必要はありません。バケットにデータを入れたり出したりするための追加のプロセスは必要ありません、cmd1
およびcmd2
自分でやる。
出力をメモリに保存したい場合は、そのためにシェルを使用することはお勧めできません。 zsh
以外の最初のシェルは、変数に任意のデータを格納できません。何らかの形式のエンコーディングを使用する必要があります。そして、そのデータを渡すために、ヒアドキュメントまたはヒア文字列を使用するときにディスクに書き込まない場合、データをメモリに数回複製することになります。
たとえば、代わりにPerl
を使用できます。
Perl -MPOSIX -e '
sub status() {return WIFEXITED($?) ? WEXITSTATUS($?) : WTERMSIG($?) | 128}
$/ = undef;
open A, "-|", "cmd1" or die "open A: $!\n";
$out = <A>;
close A;
$status = status;
exit $status if $status != 0;
open B, "|-", "cmd2" or die "open B: $!\n";
print B $out;
close B;
exit status'
これは率直に言ってひどいバージョンで、 moreutils
のさまざまなツールをつなぎ合わせています。
chronic sh -c '! { echo 123 ; false ; }' | mispipe 'ifne -n false' 'ifne echo ok'
それでもまだ十分ではありません。失敗した場合は1を返し、そうでない場合は0を返します。ただし、最初のコマンドが成功しない限り2番目のコマンドを開始せず、最初のコマンドが機能したかどうかに応じて失敗または成功したコードを返し、ファイルを使用しません。
より一般的なバージョンは次のとおりです。
chronic sh -c '! '"$CMD1" | mispipe 'ifne -n false' "ifne $CMD2"
これにより、3つのmoreutilsツールがまとめられます。
chronic
は、失敗しない限り、静かにコマンドを実行します。この場合、シェルを実行して最初のコマンドを実行し、成功/失敗の結果を反転できるようにします。コマンドは静かに実行されますif失敗し、成功した場合は最後に出力を出力します。mispipe
は2つのコマンドをパイプでつなぎ、最初のコマンドの終了ステータスを返します。これは、set -o pipefail
の効果に似ています。コマンドは文字列として提供されるため、区別することができます。ifne
は、標準入力が空でない場合、または-n
で空の場合にプログラムを実行します。 2回使用しています。
1つ目はifne -n false
です。これはfalse
を実行し、入力が空である場合に終了コードとして使用します(chronic
がそれを食べたことを意味し、cmd1
が失敗したことを意味します)。
入力が空でない場合、false
は実行されず、cat
のように入力が渡され、0で終了します。出力は、mispipe
によって次のコマンドにパイプされます。
2番目はifne cmd2
です。これは、入力が空でない場合にcmd2
を実行します。その入力はifne -n false
の出力であり、コマンドが成功したときに発生するchronic
の出力が空でない場合に正確に空になりません。
入力が空の場合、cmd2
は実行されず、ifne
はゼロを終了します。 mispipe
はとにかく終了値を破棄します。
このアプローチには(少なくとも)2つの欠陥が残っています。
cmd1
が失われ、ブール値のtrue/falseになります。終了コードに意味がある場合、それは失われます。 sh
コマンドでコードをファイルに保存し、必要に応じて後で再ロード(ifne -n sh -c 'read code <FILENAME ; rm -f FILENAME; exit $code'
など)することができます。cmd1
が出力なしで成功する可能性がある場合、とにかくすべてが崩壊します。さらに、もちろん、それはパイプでまとめられたかなりまれなコマンドであり、明白ではない意味で、慎重に引用されています。
まず、false
は標準出力に何も出力せず、echo
は標準入力から読み取らないため、例false | echo ok
は無意味です。これに対する「解決策」はfalse && echo ok
です。
cmd1 && cmd2
これはcmd1
を実行し、cmd2
が正常に実行を完了するまでcmd1
を開始しません。
次のようなパイプラインで
cmd1 | cmd2
2つのコマンドは常に同時に開始されます(これは「失敗したソリューション1」で気付くものです)。それらを同期するのは、cmd2
の出力からのcmd1
の読み取りです。パイプラインは、あるプログラムからの出力を、同時に実行されている別のプログラムの入力に渡す方法です。
cmd1
がcmd2
が読み取るものを出力していることをシミュレートするが、同時実行性を取り除くには、cmd1
からの出力をcmd2
の一時ファイルに保存する必要があります。読み取り:
cmd1 >outfile && cmd2 <outfile
一時ファイルは次のように処理できます。
trap 'rm -f "$tmpfile"' EXIT
tmpfile=$(mktemp)
cmd1 >"$tmpfile" && cmd2 <"$tmpfile"
これにより、シェルを終了するときにトリガーされるトラップが設定されます。トラップは一時ファイルを削除します。
メモリファイルシステムに$TMPDIR
がある場合、ディスクへの書き込みに対してI/Oペナルティは発生しません。
ファイルのサイズが心配な場合は、何があってもディスクに保存する必要があります(パイプも内容を保持できません。これは、「失敗したソリューション3」で気づくものです)。 。
xhienneのソリューション Bashの場合:
result=$(cmd1) && cmd2 <<< "$result"
unset result
これは、結果が空行で終わらないテキストの場合は機能しますが、nullバイトが含まれている場合は失敗します(これらはbash
によって破棄されます)。
これを軽減するために、結果をbase64エンコードすることができます。
set -o pipefail # ksh/zsh/bash
result=$( cmd1 | base64 ) && base64 -d <<<"$result" | cmd2
unset result
これは、特に結果が大きい場合($result
のbase64エンコーディングはバイナリより3分の1大きくなります)、メモリとCPUの両方の使用量の観点からひどいの考えです。バイナリ結果をディスクに書き込み、そこから読み取る方がはるかに優れています。
また、bash
は一時ファイルを使用して<<<
を実装することに注意してください。