Linuxではパイプラインを実行できますか?
cmd1 | cmd2
このように:
cmd2
cmd1
完全に完了するまで実行を開始しません。cmd1
エラーがある場合はcmd2
まったく実行されず、パイプラインの終了ステータスはですcmd1
。
たとえば、このパイプラインを作成する方法は次のとおりです。
false | echo ok
何も印刷せずにゼロ以外の状態を返しますか?
失敗した解決策1
set -o pipefail
パイプラインの終了状態はゼロではありませんが、cmd2
失敗しても引き続き実行されます。cmd1
解決策2失敗
cmd1 && cmd2
これはパイプラインではありません。 I/O リダイレクトがありません。
解決策3失敗
mkfifo /tmp/fifo
cmd1 > /tmp/fifo && cmd2 < /tmp/fifo
ブロックされます。
最適ではないソリューション
touch /tmp/file
cmd1 > /tmp/file && cmd2 < /tmp/file
これはうまくいくようです。しかし、ここにはいくつかの欠点があります。
I/O 速度が遅いディスクにデータを書き込みます。 (もちろん使えます一時ファイルシステムただし、これは追加のシステム要件です。)
一時ファイル名を慎重に選択する必要があります。それ以外の場合は、誤って既存のファイルを上書きする可能性があります。一時テーブル役に立つかもしれませんが、名前のないパイプは命名作業を完全に減らします。
一時ファイルを含むファイルシステムは、データ全体を保存するのに十分な大きさではない可能性があります。
一時ファイルは自動的にクリーンアップされません。
答え1
出力サイズはわかりませんが、cmd1
パイプは知っています。バッファサイズが制限されています。。特定の量のデータがパイプに書き込まれると、誰かがパイプを読み取るまですべての後続の書き込みがブロックされます(失敗したソリューション3に似ています)。
ブロックしないことが保証されるメカニズムを使用する必要があります。非常に大きなデータの場合は、一時ファイルを使用してください。それ以外の場合、データをメモリに保存する能力がある場合(最終的にはパイプのアイデアです)、次を使用してください。
result=$(cmd1) && cmd2 < <(printf '%s' "$result")
unset result
ここの結果はcmd1
変数に保存されますresult
。cmd1
成功したらcmd2
、データを実行して提供しますresult
。最後に、result
設定を解除して関連メモリを解放してください。
注:以前はここで文字列(<<< "$result"
)を使用してデータを提供していましたが、cmd2
Stéphane Chazelasはそれがbash
望ましくない一時ファイルを生成することを観察しました。
コメントの質問に答えてください。
はい、コマンドをリンクできますランダム:
result=$(cmd1) \ && result=$(cmd2 < <(printf '%s' "$result")) \ && result=$(cmd3 < <(printf '%s' "$result")) \ ... && cmdN < <(printf '%s' "$result") unset result
いいえ、上記のソリューションは、次の理由でバイナリデータには適していません。
- コマンド置換は
$(...)
末尾の改行を食べます。 \0
コマンド置換の結果では、NUL文字()の動作は指定されていません(たとえば、Bashはこれを削除します)。
- コマンド置換は
はい、バイナリデータに関連するこれらすべての問題を回避するには、次のエンコーダ
base64
(またはuuencode
NUL文字と末尾の改行のみを処理する自家製エンコーダ)を使用できます。result=$(cmd1 > >(base64)) && cmd2 < <(printf '%s' "$result" | base64 -d) unset result
ここでは、終了値を変更せずに
>(...)
維持するためにプロセス置換()を使用する必要があります。cmd1
つまり、データがディスクに書き込まれないようにするのはかなり面倒です。中間の一時ファイルがより良いソリューションです。バラよりスティーブンの答えこれは、これに対するほとんどの懸念を解決します。
答え2
パイピングコマンドの要点は、コマンドを同時に実行し、他のコマンドの出力を読み取ることです。順番に実行してパイプのたとえを維持する場合は、最初のコマンドの出力をバケットにパイプ(保存)してから、バケットを別のコマンドで空にする必要があります。
しかし、これを行うためにパイプを使用することは、最初のコマンドに対して2つのプロセス(コマンドとパイプのもう一方の端で出力を読み取りバケットに保存する別のプロセス)があり、2番目のコマンドに対して2つのプロセス(1つは、パイプを一端に空にするコマンド)は、もう一方の端から読み取られます。
バケットにはメモリまたはファイルシステムが必要です。メモリ拡張がうまくいかないため、パイプが必要です。ファイルシステムはより意味があります。それが/tmp
目的です。ずっと後に(一時ファイルが削除された後)までデータがフラッシュされない可能性があります。そうでなければ、当初はデータが大きすぎてメモリに入ることができないでしょう。
一時ファイルは常にシェルで使用されます。ほとんどのシェルでは、ここのドキュメントとここの文字列は一時ファイルを使用して実装されています。
存在する:
cat << EOF
foo
EOF
ほとんどのシェルは一時ファイルを作成し、書き込みと読み取りのために開いて削除し、stdinで埋め、foo
開いcat
たfdからコピーしたstdinで読み取るために実行します。ファイルはいっぱいになる前に削除されます(ファイルに記録された内容が停電後も維持される必要がないという手がかりをシステムに提供します)。
ここでも同じことができます。
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'
答え3
これは、異なるツールを一緒に組み合わせる正直ひどいバージョンです。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つの追加ユーティリティツールが付属しています。
chronic
失敗しない限り、コマンドを自動的に実行します。この場合、成功/失敗の結果を元に戻すために最初のコマンドを実行するシェルを実行しています。これにより、コマンドが自動的に実行されます。もし失敗、成功すると最後に出力を印刷します。mispipe
両方のコマンドを一緒にパイプして、最初のコマンドの終了ステータスを返します。これと同様の効果がありますset -o pipefail
。これらのコマンドは区別するために文字列として提供されます。ifne
標準入力が空でない場合、または標準入力が空の場合はプログラムを実行してください-n
。私達はそれを二度使用します:最初はです
ifne -n false
。これは、false
入力が次の場合にのみ実行され、終了コードとして使用されます。空(chronic
食べるという意味、cmd1
失敗するという意味)入力が空でない場合、実行されずに
false
likeを介して入力を渡し、cat
0で終了します。出力は次のコマンドにパイプされますmispipe
。2番目はです
ifne cmd2
。これは、cmd2
入力が次の場合にのみ実行されます。空ではありません。この入力は の出力でありifne -n false
、 の出力がchronic
null でない場合 (コマンドが成功したときに発生) 出力も null ではありません。入力が空の場合は
cmd2
実行されず、ifne
ゼロで終了します。mispipe
とにかく終了値は削除されます。
このアプローチには(少なくとも)2つの欠点がある。
- 前述のように、実際の終了コードは失われ、ブールの
cmd1
true / falseに縮小されます。終了コードが意味がある場合は失われます。コマンド内でコードをファイルに保存し、必要に応じてsh
後で再ロードできます。ifne -n sh -c 'read code <FILENAME ; rm -f FILENAME; exit $code'
- 成功しても
cmd1
出力が生成されない場合は、とにかくすべてがクラッシュします。
さらに、それは明白ではない意味を持ち、慎重に引用されたいくつかのややまれな命令がまとめられているということです。
答え4
cmd1 | cmd2
次のようにパイプラインを実行します。
cmd2
cmd1
完全に完了するまで実行を開始しないでください。
これは通常不可能です。読むパイプ(7)これは思います。パイプライン容量が制限されています。(通常4Kbytesまたは64Kbytes)彼らはいくつかを使用します。コアバッファのメモリ。
したがって、出力はcmd1
パイプに入ります。いっぱいになると書き込み(2)cmd1
toによる完了は、(非常に珍しい標準出力に対する非ブロックI / Oを処理するために特にコーディングされていないSTDOUT_FILENO
場合)ブロックされます。cmd1
cmd2
読書(2)そのパイプのもう一方の端から。始めないとcmd2
決して起きないでしょう。
この本を必ず読んでください。高度なLinuxプログラミングこれは詳細に説明されています(すべてを説明するには1冊の本が必要です)。