Hirosaji Tech Blog 🍙

Web開発の記事が多め。絵師支援の記事も少し。

ネコでもわかる set ±o pipefail

BashUNIX系のシェル)を実行する際、

$ set -o pipefail
$ set +o pipefail 

というコマンドがおまじない的に使われているのをよく見ます。

今回は、担当することになったプロダクトのCIにこのコマンドが含まれていたこともあり、処理の詳細を調べました。
その覚書もかねて、解説記事を残します。

TL;DR

  • set ±o pipefail は、パイプラインを用いるコマンドの終了ステータスに影響する。
  • set -o pipefail を使うと、パイプライン内のいずれかのコマンドが失敗した場合、その失敗したコマンドの終了ステータスがパイプライン全体の終了ステータスとなる。
  • set +o pipefail を使うと、パイプライン末尾の終了ステータスがパイプライン全体の終了ステータスとなる。(デフォルトなので無くても良い)

解説

通常、bashはコマンドが実行されるたび、終了ステータス*1を更新します。

例として、簡単なシェルスクリプトを実行してみましょう。

#!/bin/sh

true
echo $?

false
echo $?

# bashの終了ステータスは $? で確認できる

このスクリプトの実行結果は次の通りです。

0
1

一般に、コマンドが正常終了したら 0 、コマンドが異常終了したら 0 以外が終了ステータスが記録されます。

終了ステータスの範囲は 0~255 です。
よく使われるのは、次の予約済みのステータス*2でしょうか。

終了ステータス 意味
1 一般的なエラー
2 ビルトインコマンドの誤用
126 コマンドを実行できなかった
(例:実行権限がない)
127 コマンドが見つからなかった
128 exit に不正な値を渡した
(例:浮動小数点)
255 exit に範囲外の終了ステータスを渡した

では、パイプラインを使う際の終了ステータスはどう更新されるでしょう。

#!/bin/sh

true | true
echo "$?"

false | true
echo "$?"

true | false
echo "$?"
0
0
1

終了ステータスには、パイプライン末尾のコマンドの終了ステータスが渡されます。

しかし2番目のパイプラインから分かるように、先頭のコマンドがエラーなのに、終了ステータスが正常終了を表す 0 になっては困るときが多いでしょう。
そんな時に、set -o pipefail が役立ちます。

では、 set -o pipefail を冒頭で宣言して、同様のスクリプトを実行してみましょう。

#!/bin/sh

set -o pipefail

true | true
echo "$?"

false | true
echo "$?"

true | false
echo "$?"
0
1
1

上記の通り、実行を失敗したコマンドの終了ステータスが変わっていることがわかります。
これで、パイプライン内のコマンドに対してもエラー制御しやすくなりました。

ちなみに、デフォルトは set +o pipefail です。
デフォルトですが、可読性のためにあえて明示的に set +o pipefail を宣言してもいいですし、スクリプトの途中で ± を切り替えるために使っても良いかもしれませんね。

少しだけDeep Dive

set -o pipefail を使うと、パイプライン内で失敗したコマンドの中でもより後者の終了ステータスが反映されるようです。

#!/bin/sh

set -o pipefail

false | not-exist-command | true
echo "$?"

not-exist-command | false | true
echo "$?"
127
1

また、set -o pipefail の他にも、set -e -u -x pipefail と色々とバリエーションがあります。
気になる方は次の参考リンクなどから調べてみてください。

*1:直前に実行されたコマンドの状態を表す数値(英: exit status)

*2:Exit Codes With Special Meaningsより抜粋