PIPE で使用される Bash 終了ステータス

PIPE で使用される Bash 終了ステータス

パイプが使用されるときに終了ステータスがどのように伝達されるかを理解しようとしています。which存在しないプログラムを見つけるために使用していると仮定します。

which lss
echo $?
1

which見つけられなかったため、lss終了ステータス 1 を取得しました。これは問題ありません。ただし、次の操作を試みると:

which lss | echo $?
0

これは、最後に実行されたコマンドが正常に終了したことを示します。私がこれを理解できる唯一の方法は、おそらく PIPE も終了ステータスを生成するということです。これは正しい理解方法でしょうか?

答え1

パイプの終了ステータスは、右側のコマンドの終了ステータスです。左側のコマンドの終了ステータスは無視されます。

( ではwhich lss | echo $?これが表示されないことに注意してください。which lss | true; echo $?これを表示するには、 を実行します。 ではwhich lss | echo $?echo $?このパイプラインの前の最後のコマンドのステータスが報告されます。)

シェルがこのように動作する理由は、左側のエラーを無視する必要があるというシナリオがかなり一般的だからです。左側がまだ書き込み中に右側が終了した場合 (またはより一般的には標準入力を閉じた場合)、左側は SIGPIPE シグナルを受け取ります。この場合、通常は何も問題はありません。右側はデータに関心がなく、左側の役割がこのデータを生成することだけであれば、停止しても問題ありません。

ただし、左側が SIGPIPE 以外の何らかの理由で停止した場合、または左側の仕事が標準出力にデータを生成することだけではなかった場合、左側のエラーは報告されるべき真のエラーです。

単純な sh では、唯一の解決策は名前付きパイプを使用することです。

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

ksh、bash、zsh では、パイプラインのコンポーネントが非ゼロのステータスで終了した場合に、シェルにパイプラインを非ゼロのステータスで終了するように指示できます。オプションを設定する必要がありますpipefail

  • ksh:set -o pipefail
  • バッシュ:shopt -s pipefail
  • zsh: setopt pipefail(またはsetopt pipe_fail)

mksh、bash、zsh では、変数PIPESTATUS(bash、mksh) またはpipestatus(zsh) を使用して、パイプラインの各コンポーネントのステータスを取得できます。これらの変数は、最後のパイプラインのすべてのコマンドのステータスを含む配列です (の一般化$?)。

答え2

パイプラインの終了ステータスは、パイプラインの最後のコマンドの終了ステータスです (ただし、pipefailそれをサポートするシェルでシェル オプションが設定されている場合は、終了ステータスは、パイプラインの最後のコマンドの終了ステータス (ゼロ以外のステータスで終了) になります)。

パイプラインの終了ステータスを出力することはできません内部からパイプラインは、実際に実行が完了するまでその値がどうなるかを知る方法がないため、パイプラインではありません。

パイプライン

which lss | echo $?

は標準入力を読み込まないので意味がありませんecho(パイプラインは、あるコマンドの出力から次の入力にデータを渡すために使用されます)。echoまた、パイプラインの終了ステータスは表示されませんが、すぐに実行されるコマンドの終了ステータスが表示されます。前にパイプライン。

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

これはより良い例です:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

つまり、パイプラインは以下でも使用できますif

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi

答え3

はい、PIPEは終了ステータスを生成します。PIPEの前のコマンドの終了コードが必要な場合は、次のようにします。$PIPESTATUS

によるとこのStack OverflowのQ&Aパイプを使用するときにコマンドの終了ステータスを出力するには、次のようにします。

your_command | echo $PIPESTATUS

または、 コマンドを使用して pipefail を設定しset -o pipefail(設定を解除するには を実行set +o pipefail)、$?の代わりにを使用します$PIPESTATUS

your_command | echo $?

これらのコマンドは、少なくとも 1 回実行した後にのみ機能します。つまり、PIPESTATUS次のようにパイプを使用してコマンドの後に実行した場合にのみ機能します。

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

${PIPESTATUS[0]}は 10 個、 は 1 個獲得できます${PIPESTATUS[1]}このU&L Q&A詳細については。

答え4

シェルは、パイプラインが実行される前にコマンド ラインを評価します。したがって、$?パイプラインの前に実行された最後のコマンドの値も評価されます。シェルecho自体がパイプラインの前に開始されるか、後に開始されるかはwhich関係ありません。

ここで、質問が具体的に終了ステータスの伝播に関するものであれば、次のようにしてデフォルトの動作を調べることができます。

% false | true
% echo $?
0

% true | false
% echo $?
1

ご覧のとおり、パイプラインの終了ステータスは、最後のコマンドの終了ステータスです。

関連情報