どーも!marusukeです!
シェルスクリプトの冒頭でよく見かけるset -euo pipefailについて、何をしているのか・なぜ必要なのかを説明します!
set -euo pipefailでできること
#!/bin/bash
set -euo pipefail
これは-e・-u・-o pipefailという3つの設定をまとめて有効にする書き方です。setコマンド自体の使い方や、他のオプション(-xや-nなど)についてはsetコマンドの使い方で解説していますので、この記事では自動化スクリプトで特に重要な、この3つの組み合わせに絞って説明します。
| オプション | 正式名 | 効果 |
|---|---|---|
-e | errexit | コマンドが失敗(終了コードが0以外)した時点でスクリプトを止める |
-u | nounset | 未定義の変数を使おうとした時点でエラーにする |
-o pipefail | pipefail | パイプの途中のコマンドが失敗した場合も、パイプ全体を失敗扱いにする |
自動化スクリプトは人が張り付いて見ているわけではないので、「途中の処理が失敗したのに、何事もなかったように最後まで走ってしまう」のが一番怖いパターンです。この3つは、そうした事故を早い段階で止めるための設定です。
何も指定しない場合に起きる事故
それぞれ、指定しなかった場合にどんな事故が起きるかを見てみましょう。
1. -eが無いと:失敗したコマンドを無視して先に進んでしまう
#!/bin/bash
cp /path/to/missing_file /backup/
echo "バックアップが完了しました"
-eが無い場合、cpがファイルの不在で失敗しても、シェルはそれを無視して次の行に進みます。結果として、実際にはバックアップが失敗しているのに「バックアップが完了しました」と表示されてしまいます。
#!/bin/bash
set -e
cp /path/to/missing_file /backup/
echo "バックアップが完了しました"
-eがあれば、cpが失敗した時点でスクリプトが終了し、「完了しました」の行は実行されません。
2. -uが無いと:変数名のタイプミスに気づけない
#!/bin/bash
BACKUP_DIR=/backup
rm -rf "$BAKUP_DIR"/*
BACKUP_DIRのつもりがBAKUP_DIRとタイプミスしています。-uが無いと、未定義の変数は空文字として扱われるため、rm -rf /*が実行されてしまう非常に危険な例です(実際には他の安全策も必要ですが、変数名のミスに気づけないこと自体が事故の入口になります)。
#!/bin/bash
set -u
BACKUP_DIR=/backup
rm -rf "$BAKUP_DIR"/*
-uがあれば、BAKUP_DIRは未定義の変数として扱われ、この行でエラーになってスクリプトが止まります。タイプミスにその場で気づけます。
3. -o pipefailが無いと:パイプの途中の失敗が握りつぶされる
cat missing_file.txt | grep "ERROR"
echo $? # → 1(grepが「見つからなかった」ときの終了コード)
パイプでコマンドをつなぐと、デフォルトではパイプ全体の終了コードは最後のコマンド(この例ではgrep)のものになります。catがファイル不在で失敗していても、それは終了コードに反映されません。
set -o pipefail
cat missing_file.txt | grep "ERROR"
echo $? # → catの失敗を反映した終了コードになる
pipefailを有効にすると、パイプの中のどれか1つでも失敗すれば、パイプ全体が失敗として扱われます(複数が失敗した場合は、一番右側で失敗したコマンドの終了コードになります)。ログをcatやgrepでつないで判定するような自動化スクリプトでは、これが無いと失敗を見逃す原因になります。
意図的に失敗を許容したいとき
set -eはとても便利ですが、「このコマンドは失敗してもいい」という場面もあります。その場合は|| trueを使います。
set -e
grep "ERROR" access.log || true
echo "ここまで到達します"
grepがエラー文字列を見つけられずに失敗(終了コード1)しても、|| trueによって全体の終了コードが0になるため、set -eによってスクリプトが止まることはありません。
一時的に-eを無効にしたい場合は、set +eで解除し、set -eで戻すこともできます。
set +e
some_command_that_may_fail
set -e
主なオプション(この記事で扱う3つ)
| オプション | 説明 |
|---|---|
-e | コマンドが失敗した時点でスクリプトを終了する |
-u | 未定義の変数を参照した時点でエラーにする |
-o pipefail | パイプの途中のコマンドが失敗した場合も、パイプ全体を失敗として扱う |
+e | -eを解除する(一時的にエラーを許容したい範囲で使う) |
-x(実行したコマンドを表示するデバッグモード)や-n(構文チェックのみ)など、その他のオプションはsetコマンドの使い方にまとめています。
補足・注意点:
-e(およびERRトラップ)は、実はすべての失敗を捕まえてくれるわけではありません。if文の条件式やwhile・untilの条件、&&・||でつないだコマンド(最後を除く)、!を前置きしたコマンドが失敗した場合には発火しません。「set -eをつけているのに止まらない」と感じたときは、まずこの条件に当てはまっていないか確認してみてください。
自動化スクリプトでの書き方の型
以上を踏まえると、自動化スクリプトの先頭は次の形にしておくのが定番です。
#!/bin/bash
set -euo pipefail
# ここから実際の処理
スクリプトの終了時に一時ファイルの削除やログ出力を確実に行いたい場合は、trapコマンドと組み合わせるとさらに安全になります。
#!/bin/bash
set -euo pipefail
trap 'echo "エラーが発生しました(行番号: $LINENO)" >&2' ERR
関連コマンド
setコマンド全体のオプションはsetコマンドの使い方、エラー発生時・終了時の後片付けはtrapコマンドの使い方をご覧ください。自動化スクリプトで使うコマンドはbash自動化スクリプトで使えるコマンド集にまとめています。
まとめ
set -euo pipefailは、-e(コマンド失敗で停止)・-u(未定義変数でエラー)・-o pipefail(パイプ途中の失敗を無視しない)の3つをまとめて有効にする、自動化スクリプトの定番の書き方です。人が張り付いて見ていない自動化スクリプトだからこそ、まず冒頭にこの1行を入れておくことをおすすめします。
以上です! ここまで読んでいただきありがとうございました!
https://marusuke-blog.com/linux-command-set/
https://marusuke-blog.com/linux-command-trap/
https://marusuke-blog.com/linux-summary-bash-automation/

コメント