失敗した解決策1

失敗した解決策1

Linuxではパイプラインを実行できますか?

cmd1 | cmd2

このように:

  1. cmd2cmd1完全に完了するまで実行を開始しません。

  2. 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

これはうまくいくようです。しかし、ここにはいくつかの欠点があります。

  1. I/O 速度が遅いディスクにデータを書き込みます。 (もちろん使えます一時ファイルシステムただし、これは追加のシステム要件です。)

  2. 一時ファイル名を慎重に選択する必要があります。それ以外の場合は、誤って既存のファイルを上書きする可能性があります。一時テーブル役に立つかもしれませんが、名前のないパイプは命名作業を完全に減らします。

  3. 一時ファイルを含むファイルシステムは、データ全体を保存するのに十分な大きさではない可能性があります。

  4. 一時ファイルは自動的にクリーンアップされません。

答え1

出力サイズはわかりませんが、cmd1パイプは知っています。バッファサイズが制限されています。。特定の量のデータがパイプに書き込まれると、誰かがパイプを読み取るまですべての後続の書き込みがブロックされます(失敗したソリューション3に似ています)。

ブロックしないことが保証されるメカニズムを使用する必要があります。非常に大きなデータの場合は、一時ファイルを使用してください。それ以外の場合、データをメモリに保存する能力がある場合(最終的にはパイプのアイデアです)、次を使用してください。

result=$(cmd1) && cmd2 < <(printf '%s' "$result")
unset result

ここの結果はcmd1変数に保存されますresultcmd1成功したらcmd2、データを実行して提供しますresult。最後に、result設定を解除して関連メモリを解放してください。

注:以前はここで文字列(<<< "$result")を使用してデータを提供していましたが、cmd2Stéphane Chazelasはそれがbash望ましくない一時ファイルを生成することを観察しました。

コメントの質問に答えてください。

  • はい、コマンドをリンクできますランダム:

    result=$(cmd1) \
    && result=$(cmd2 < <(printf '%s' "$result")) \
    && result=$(cmd3 < <(printf '%s' "$result")) \
    ...
    && cmdN < <(printf '%s' "$result")
    unset result
    
  • いいえ、上記のソリューションは、次の理由でバイナリデータには適していません。

    1. コマンド置換は$(...)末尾の改行を食べます。
    2. \0コマンド置換の結果では、NUL文字()の動作は指定されていません(たとえば、Bashはこれを削除します)。
  • はい、バイナリデータに関連するこれらすべての問題を回避するには、次のエンコーダbase64(またはuuencodeNUL文字と末尾の改行のみを処理する自家製エンコーダ)を使用できます。

    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失敗するという意味)

      入力が空でない場合、実行されずにfalselikeを介して入力を渡し、cat0で終了します。出力は次のコマンドにパイプされますmispipe

    • 2番目はですifne cmd2。これは、cmd2入力が次の場合にのみ実行されます。空ではありません。この入力は の出力でありifne -n false、 の出力がchronicnull でない場合 (コマンドが成功したときに発生) 出力も null ではありません。

      入力が空の場合はcmd2実行されず、ifneゼロで終了します。mispipeとにかく終了値は削除されます。


このアプローチには(少なくとも)2つの欠点がある。

  1. 前述のように、実際の終了コードは失われ、ブールのcmd1true / falseに縮小されます。終了コードが意味がある場合は失われます。コマンド内でコードをファイルに保存し、必要に応じてsh後で再ロードできます。ifne -n sh -c 'read code <FILENAME ; rm -f FILENAME; exit $code'
  2. 成功してもcmd1出力が生成されない場合は、とにかくすべてがクラッシュします。

さらに、それは明白ではない意味を持ち、慎重に引用されたいくつかのややまれな命令がまとめられているということです。

答え4

cmd1 | cmd2次のようにパイプラインを実行します。

cmd2cmd1完全に完了するまで実行を開始しないでください。

これは通常不可能です。読むパイプ(7)これは思います。パイプライン容量が制限されています。(通常4Kbytesまたは64Kbytes)彼らはいくつかを使用します。コアバッファのメモリ。

したがって、出力はcmd1パイプに入ります。いっぱいになると書き込み(2)cmd1toによる完了は、(非常に珍しい標準出力に対する非ブロックI / Oを処理するために特にコーディングされていないSTDOUT_FILENO場合)ブロックされます。cmd1cmd2読書(2)そのパイプのもう一方の端から。始めないとcmd2決して起きないでしょう。

この本を必ず読んでください。高度なLinuxプログラミングこれは詳細に説明されています(すべてを説明するには1冊の本が必要です)。

関連情報