1. 概要

Bash では、コマンドをパイプ処理し、パイプライン内のコマンドの1つの終了ステータスを確認したい場合があります。 例としては、実行中に出力を確認したい長時間実行プロセスがあります。

$ long_running_script.sh 2>&1 | tee output_of_script

この例では、おそらくスクリプトの終了ステータス long_running_script.sh。に関心がありますが、パイプされたコマンドの実行直後に echo $?コマンドを実行すると終了します。 teeコマンドのステータス。

このチュートリアルでは、パイプされたコマンドの終了ステータスの取得について説明します。

2. 問題の分析

まず、パイプされたコマンドの終了ステータスの分析から始めましょう。 次のスクリプトhello_world.shがあります。

#!/bin/bash
echo "Hello World"
exit $1

このBashスクリプトは、 Hello World を出力し、スクリプトにパラメーターとして提供された終了ステータスで終了します。

$ hello_world.sh 0
Hello World
$ echo $?
0
$ hello_world.sh 5
Hello World
$ echo $?
5

次に、 grep コマンドを使用して、このスクリプトの出力で Hello World を検索し、パイプラインの終了ステータスを確認します。

$ hello_world.sh 5 | grep "Hello World"
Hello World
$ echo $?
0

ご覧のとおり、hello_world.shスクリプトの終了ステータスは5ですが、パイプされたコマンドの終了ステータスは0です。 実際、パイプされたコマンドの終了ステータスは、パイプされたコマンドの最後のコマンドの終了ステータスです。 この例では、 grep“ Hello World”コマンドの終了ステータスです。 スクリプトの出力に存在しない単語を検索して、それを証明しましょう。たとえば、 Hello Universe

$ hello_world.sh 5 | grep "Hello World" | grep "Hello Universe"
$ echo $?
1

これで、パイプラインの最後のコマンド grep“ Hello Universe”、の終了ステータスが 1 であるため、パイプされたコマンドの終了ステータスは1になります。

3. PIPESTATUS変数

BashのPIPESTATUS環境変数は、パイプライン内の各コマンドの終了ステータスを取得するために役立ちます。 $PIPESTATUSは配列です。 各コマンドの終了ステータスをパイプラインに保存します。

$ hello_world.sh 5 | grep "Hello World" | grep "Hello Universe"
$ echo ${PIPESTATUS[@]}
5 0 1

コマンドecho${PIPESTATUS [@]}は、配列PIPESTATUSのすべての要素を取得します。配列の最初の要素( 5 )は、の終了ステータスです。 hello_world.sh5コマンド。 同様に、配列の2番目の要素( 0 )は、 grep“ Hello World”コマンドの終了ステータスです。 最後に、配列の3番目の要素( 1 )は、grep「HelloUniverse」コマンドの終了ステータスです。

もちろん、配列の対応するインデックスを使用して、関心のあるコマンドの終了ステータスを取得できます。 配列の最初の要素のインデックスは0です。

$ hello_wold.sh 5 | grep "Hello World" | grep "Hello Universe"
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}
5 0 1

コマンドを実行するたびにPIPESTATUS変数が更新されることに注意する必要があります。

$ hello_world.sh 5 | grep "Hello World" | grep "Hello Universe"
$ echo ${PIPESTATUS[2]}
1
$ echo ${PIPESTATUS[0]}
0

この場合、 PIPESTATUS [0] は、予想とは異なる終了ステータスを返しました。 コマンドの実行のためエコー${PIPESTATUS[0]} 前のコマンドの終了ステータスを返しました。 ${PIPESTATUS[2]}をエコーします。 動作は次のようなものですエコー$? 指図。 $?は、前のコマンドの終了ステータスです。 その値は、コマンドの実行ごとに更新されます。 したがって、 echo $ {PIPESTATUS [0]} コマンドは、パイプを使用しない場合、 echo $?コマンドと同じ終了ステータスを返します。

$ hello_world.sh 5
Hello World
$ echo $?
5
$ hello_world.sh 5
Hello World
$ echo ${PIPESTATUS[0]}
5

4. 各コマンドの終了ステータスの保存

BashがPIPESTATUS変数を提供しなかった場合はどうなりますか? まあ、人生はあるが、希望はある。 その場合、各コマンドの終了ステータスを保存して後で確認できます

$ { hello_world.sh 5; echo $? > exit_status_1; } | { grep "Hello World"; echo $? > exit_status_2; } | { grep "Hello Universe"; echo $? > exit_status_3; }
$ cat exit_status_1
5
$ cat exit_status_2
0
$ cat exit_status_3
1

この例では、 各コマンドをecho$でグループ化しましたか? 指図 。 したがって、3つのコマンドグループを作成しました。 各コマンドの終了ステータスをファイルに保存しました。 たとえば、コマンド hello_world.sh5の終了ステータスをファイルexit_status_1に保存しました。 パイプラインでコマンドグループを使用すると、各グループの出力が次のグループの入力にリダイレクトされます。 ご覧のとおり、各コマンドの終了ステータスは、PIPESTATUS変数を使用して取得したものと同じです。

5. setコマンドのpipefailオプション

パイプラインの終了ステータスは、パイプラインの最後のコマンドの終了ステータスであることはすでにわかっています。 たとえば、前に見たように、次のパイプラインは終了ステータス 0で終了しました。ただし、パイプラインの最初のコマンド hello_world.sh 5 は、終了ステータスで終了しました。 X194X] 5 :

$ hello_world.sh 5 | grep "Hello World"
Hello World
$ echo $?
0

場合によっては、パイプラインがゼロ以外の値で終了するパイプライン内のコマンドの終了ステータスを返すようにしたいことがあります。 この目標には、setコマンドのpipefailオプションを使用できます。

setコマンドは組み込みのLinuxコマンドです。 環境変数の名前と値を表示および設定します。 また、シェルのいくつかの動作を有効または無効にする多くのオプションがあります。 pipefailオプションはその1つです。 デフォルトでは無効になっています。 この場合、パイプラインの終了ステータスは、パイプラインの最後のコマンドの終了ステータスです。 ただし、これを有効にすると、パイプラインの終了ステータスは、ゼロ以外の終了ステータスで終了するパイプラインの右端のコマンドの終了ステータスになります。 set –o pipefail コマンドはこの動作を有効にし、 set + opipefailコマンドはこの動作を無効にします。

$ set -o pipefail
$ hello_world.sh 5 | grep "Hello World"
Hello World
$ echo $?
5
$ set +o pipefail
$ hello_world.sh 5 | grep "Hello World"
Hello World
$ echo $?
0

最後の例でset–opipefailコマンドを使用してpipefailオプションを有効にすると、パイプラインの終了ステータスは5になります。 これは、hello_world.shスクリプトの終了ステータスです。 ただし、 set + opipefailコマンドでpipefailオプションを無効にすると、パイプラインの終了ステータスは、パイプラインの最後のコマンドgrepの終了ステータスになります。 「HelloWorld」。

パイプラインの終了ステータスが、ゼロ以外の終了ステータスで終了するパイプラインの右端のコマンドの終了ステータスであることを示すために、次のコマンドを実行してみましょう。

$ set -o pipefail
$ hello_world.sh 5 | grep "Hello Universe" | (grep Hello || true)
$ echo ${PIPESTATUS[@]}
5 1 0
$ hello_world.sh 5 | grep "Hello Universe" | (grep Hello || true)
$ echo $?
1

ここで、コマンド hello_world.sh 5およびgrep“ Hello Universe” の終了ステータスは両方ともゼロ以外であり、5および1、それぞれ。 ただし、 grep“ Hello Universe” はゼロ以外の終了ステータスを持つ右端のコマンドであるため、パイプラインの終了ステータスは1です。

6. 結論

このチュートリアルでは、別のコマンドにパイプされているコマンドの終了ステータスを取得するいくつかの方法について説明しました。

Bashには、パイプライン内の各コマンドの終了ステータスを格納する配列変数PIPESTATUSがあることがわかりました。 また、パイプラインに各コマンドの終了ステータスを保存する方法についても説明しました。 最後に、setコマンドのpipefailオプションについて説明しました。