このすみろぐ

とあるWebエンジニアが、技術や趣味について書くブログです。

シェルスクリプト内のエラーで実行を停止する「-e」と、実行コマンドを出力する「-x」オプションを使ったシェルスクリプトの開発

「ソフトウェアデザイン2020年1月号」を読んでいるのですが、その中に次の一節がありました。

GitLab のCI に関する部分は.gitlab-ci.yml に書き、単独でも実行できる内容はシェルにまとめて、scriptで呼び出すのがお勧めです。
そうすると、.gitlab-ci.ymlに必要な事項だけがまとまって見やすくなるはずです。
しかし、シェルスクリプトにまとめてしまうと実行経過が見えなくなったりするので、シェルスクリプト内で実行過程を標準出力に出力するか、シェルスクリプトのshebang注C に「set -x」または 「set -xe」を付けることをお勧めします。
ソフトウェアデザイン 2020年1月号 より

GitLab特集の記事ですが、CI上で実行するシェルスクリプトには、「set -x」または「set -xe」を付けようという内容です。

なぜ「set -x」または「set -xe」なのか、少し深堀ってみます。 これが理解できるようになると、CI以外の環境であっても、重要なオプションであることがわかります。

なお本記事では、「/bin/bash」を使用しております。

「-e」オプション(bash -e / set -e)

errexit -e:パイプやサブシェルで実行したコマンドが1つでもエラーになったら、直ちにシェルを終了する
https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html より

シェルスクリプトを実行する際に「-e」を追加すると、エラーが起きたコマンドの時点でスクリプトが終了するようになります。

#!/bin/bash -e

# 存在しないファイルを閲覧しようとする
cat ./test1

# 以下は実行されない
cat ./test2
cat ./test3
$ /bin/bash -e test.sh
cat: ./test1: No such file or directory
$ echo $?
1 # 1が出力されるため、test.shが成功しなかったことがわかります

または「set -e」を使っても、同様にエラーが発生した時点で処理を止めることができます。

#!/bin/bash

# -o:シェルオプションを有効にする
set -e

# 存在しないファイルを閲覧しようとする
cat ./test1

# 以下は実行されない
cat ./test2
cat ./test3

# +o:シェルオプションを無効にする(-oによる設定を打ち消す)
set +e

「-e」オプションを使わない場合

自力で頑張る場合は、コマンドの終了ステータスを判定することによって、制御が可能です。

#!/bin/bash

cat ./test1

# コマンドの終了ステータスが「0」以外であれば、コマンドは成功していない
cmdstatus=$?
if [ $cmdstatus -ne 0 ]; then
    echo "command error!!"
    # 必要なら、ここで異常系フローの後処理ができる

    # 実行を終了させる
    exit $cmdstatus
fi

# 以下は実行されない
cat ./test2
cat ./test3

「-x」オプション(bash -x / set -x)

xtrace -x: トレース情報として、シェルが実行したコマンドとその引数を出力する。情報の先頭にはシェル変数PS4の値を使用
https://www.atmarkit.co.jp/ait/articles/1805/10/news023.html より

シェルスクリプトを実行する際に「-x」を追加すると、シェルが実行したコマンドを出力します。

#!/bin/bash -x

cat ./test1
cat ./test2
cat ./test3
$ /bin/bash -x test.sh
+ cat ./test1
cat: ./test1: No such file or directory
+ cat ./test2
cat: ./test2: No such file or directory
+ cat ./test3
cat: ./test3: No such file or directory

スクリプト内に「set -x」を記述する場合は、トレースしたい箇所の前後に追加します。

#!/bin/bash

set -x
cat ./test1
cat ./test2
cat ./test3
set +x

「-ex」は、シェルスクリプトのデバッグで役に立つ

これら2つのオプションは、組み合わせて使うことができます。

「-e」オプションによりエラーが起きた時点でスクリプトが終了しますが、「-x」オプションにより実行コマンドが逐一出力されるため、開発中やデバッグ時はとくに便利です。

#!/bin/bash -ex

# 存在しないファイルを閲覧しようとする
cat ./test1

# 以下は実行されない
cat ./test2
cat ./test3
$ /bin/bash -ex test.sh
+ cat ./test1
cat: ./test1: No such file or directory
# 「cat ./test1」でエラーが起きて失敗したことが、ひと目でわかる

CI上で実行されるシェルスクリプトのデバッグ

CI上でシェルスクリプトを動かす場合、基本的には自分の手の届かない範囲でスクリプトが実行さます。

でもシェルスクリプトの実行にデバッグオプションを付けておけば、CIのログを確認することで、スクリプトの実行確認やエラーの追跡がとてもやりやすくなります。

さいごに

アプリケーションのプログラミングでは、「try〜catch」をはじめとするエラー制御が必要であるという認識は普及しています。

一方で、シェルスクリプトにおいてもエラー処理や異常系を考慮して開発する必要があります。 これは意外と抜け落ちやすいので、忘れないようにしましょう(・・という戒めを込めた、自分向けの記事だったりします)。

ちなみに「ソフトウェアデザイン2020年1月号」にはGitLab特集があるのですが、私もGitLabは好きです。 実際に業務で使ってますが、CIがとても書きやすいです。