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


10

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

which lss
echo $?
1

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

which lss | echo $?
0

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


2
パイプラインのすべてのコンポーネントは同時に実行されます。したがって、ではwhich lss | echo $?、は終了するechowhichに開始されます。のコマンドラインを生成するシェルechowhichは、後で終了ステータスがどうなるかを知る方法がありません。
Charles Duffy、

それらは同時に実行されません。パイプの目的は、1つのコマンドの出力を別のコマンドにリダイレクトすることです。シェルが実行した最後のコマンド(つまり、ステートメントの最後のコマンド)は、戻りコードを返すコマンドになります。
Tim S.

3
@TimS。ただし、同時に実行されるため、非常に長時間実行されるコマンドの場合、最初のコマンドが完了する前に出力の最初のビットを取得できます。
Random832

実行の開始について考えていたと思います-明確なオーバーラップがありますが、プロセスは一緒に開始しません。私はあなたが何を意味するか、私の間違いを理解しています。(パイプは実行ではなく出力をリダイレクトしています...)私はコメントを失敗させましたが、うまくいけば、私が何を意味するか知っています。:)
Tim S.

そうですが、実行の開始は重要ではありません。重要なのは終了です。最後のコマンドは最初のコマンドが終了する前に開始します
user371366 '21

回答:


14

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

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

パイプライン

which lss | echo $?

echo標準入力を読み取らないため、意味がありません(パイプラインは、1つのコマンドの出力から次のコマンドの入力にデータを渡すために使用されます)。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

10

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

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
  • bash: shopt -s pipefail
  • zsh:(setopt pipefailまたはsetopt pipe_fail

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


Gillesに感謝します、私はこれらの複雑さが関与していることを知りませんでした。これにより、これがさらに明確になりました。
sshekhar1980

4

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

このスタックオーバーフローのQ&Aによれば、パイプを使用するときにコマンドの終了ステータスを出力するには、次のようにします。

your_command | echo $PIPESTATUS

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

your_command | echo $?

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

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

の場合は10 ${PIPESTATUS[0]}、の場合は1になり${PIPESTATUS[1]}ます。詳細については、このU&L Q&Aを参照してください。


2
一方でPIPESTATUS最後のパイプラインのコマンドの終了ステータスが含まれている、あなたの最初の例では、より任意のより多くの意味がありませんsomecmd | echo $?Kusalanandaが彼らの答えで取り扱います。の終了ステータスをfalse; true | echo $PIPESTATUS出力1しますfalse
ilkkachu

PIPESTATUSとpipefiailに賛成票を投じますが、これ|exhoは本当に混乱します
eckes

0

シェルは、パイプラインが実行される前にコマンドラインを評価します。だから、$?パイプラインの前に、最後に実行したコマンドの値です。かどうかecho自体は前または後に開始されるwhich無関係です。

ここで、特に終了ステータスの伝搬について質問した場合は、次のようにデフォルトの動作を調べることができます。

% false | true
% echo $?
0

% true | false
% echo $?
1

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.