set -xコマンドでシェルスクリプトをデバッグする方法|set -e・set -uとの組み合わせも

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips, シェルスクリプト > set -xコマンドでシェルスクリプトをデバッグする方法|set -e・set -uとの組み合わせも
「シェルスクリプトを書いたのに、なぜかエラーになる」
「どこで止まっているのか、原因が全くわからない」

シェルスクリプトのデバッグは、初心者だけでなく中級者でも手が止まりやすいポイントです。Linuxサーバーの運用自動化を進めるほど、スクリプトが複雑になり、バグの原因追跡が難しくなります。

この記事では、Bashの組み込みオプション set -x(トレースモード)を中心に、set -e(エラー即終了)・set -u(未定義変数をエラーに)との組み合わせによるデバッグ手法を解説します。RHEL 9.4 / Ubuntu 24.04 LTS で動作確認済みの実践的な内容です。

この記事のポイント

・set -x を使うと実行コマンドがトレース表示されデバッグが容易になる
・set -e でスクリプトをエラー発生時に即終了させ被害を最小化できる
・set -u で未定義変数を早期検知し変数名ミスのバグを防げる
・3つを組み合わせた set -eux が現場のシェルスクリプト冒頭の定番形式


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

なぜシェルスクリプトのデバッグは難しいのか

Bashスクリプトはデフォルトでは「エラーが起きても処理を続行する」という仕様になっています。つまり、途中でコマンドが失敗しても何事もなかったように次の行に進んでしまうのです。

例えば次のスクリプトを考えてみてください。

#!/bin/bash # バグを含むスクリプトの例 cp /etc/hosts /tmp/hosts_backup rm /tmp/hossts_backup # hostsをhosstsとスペルミス echo "バックアップ完了"

このスクリプトを実行すると「バックアップ完了」と表示されますが、実際には rm の対象ファイルが存在せずエラーになっています。しかしスクリプトはそのまま続行し、最終的にバックアップが残ったままになります。

このような「失敗しているのに成功したように見える」状況は、本番環境では深刻な事故につながります。Postfix の設定DNS 設定の自動化スクリプトでこの問題が発生すると、サービス停止につながりかねません。

set -x の基本的な使い方

1. set -x とは何か

set -x はBashのシェルオプションの一つで、「トレースモード(xtrace)」とも呼ばれます。このオプションを有効にすると、スクリプト内で実行される全てのコマンドが実行前に標準エラー出力(stderr)へ表示されます。

表示形式は「+ コマンド」の形式です。+ マークが、実行された(またはこれから実行される)コマンドを示しています。

2. スクリプト内で有効にする方法

最もよく使う方法は、スクリプトの先頭で set -x を宣言することです。

#!/bin/bash set -x BACKUP_DIR="/tmp/backup" mkdir -p "$BACKUP_DIR" cp /etc/hosts "$BACKUP_DIR/hosts" echo "バックアップ完了"

このスクリプトを実行すると、以下のようなトレース出力が得られます。

$ bash debug_test.sh + BACKUP_DIR=/tmp/backup + mkdir -p /tmp/backup + cp /etc/hosts /tmp/backup/hosts + echo 'バックアップ完了' バックアップ完了

各行の先頭の + マークが「set -x で表示されたトレース行」を示しています。変数への代入も含め、全ての操作が順番に記録されるため、どこで何が起きているかが一目でわかります。

3. コマンドラインオプションで有効にする方法

スクリプトを修正せずにデバッグしたい場合は、実行時に -x オプションを渡す方法が便利です。

# スクリプトを直接実行する場合 bash -x スクリプト名.sh # shebangがある場合も同様 bash -x /usr/local/bin/backup.sh

この方法なら既存スクリプトに手を加えることなくトレースを取得できるため、本番環境に配備済みのスクリプトのデバッグにも活用できます。

4. 特定の箇所だけトレースを有効にする

スクリプト全体ではなく、問題が疑われる部分だけトレースしたい場合は、set -xset +x(無効化)を組み合わせます。

#!/bin/bash echo "ここはトレースしない" set -x # トレース開始 DB_HOST="db.example.com" mysql -h "$DB_HOST" -u root -p"${DB_PASS}" mydb -e "SHOW TABLES;" set +x # トレース終了 echo "ここもトレースしない"

パスワードを含むコマンドをトレースすると、ログにパスワードが平文で記録されてしまいます。機密情報を扱う箇所は set +x で一時的にトレースを止めるのが現場の鉄則です。

set -e でエラー時に即終了させる

5. set -e とは何か

set -e(errexit)は、スクリプト内のコマンドが0以外の終了コードを返したとき、スクリプト全体を即座に終了させるオプションです。

「失敗しても続行する」というBashのデフォルト動作を変え、「失敗したら止まる」という安全な動作に切り替えます。

#!/bin/bash set -e mkdir /tmp/mydir cp /etc/hosts /tmp/mydir/ # ここで cp が失敗すると、以降の処理は実行されない echo "ファイルコピー完了"

6. set -e の注意点

set -e が有効な状態でも「終了コードを無視したい」場合があります。その場合は || true または || : を使います。

#!/bin/bash set -e # grep が見つからなくても続行させたい場合 grep "WARN" /var/log/messages || true # コマンドが失敗しても続けたい場合 rm -f /tmp/old_lock || true echo "処理完了"

また、if文の条件部分では、失敗した場合に else へ分岐するため set -e の影響を受けません。

#!/bin/bash set -e # if の条件部はエラーでもスクリプトが終了しない if grep -q "ERROR" /var/log/app.log; then echo "エラーを検知しました" fi

set -u で未定義変数をエラーにする

7. set -u とは何か

set -u(nounset)は、未定義の変数を参照したときにエラーとして扱うオプションです。

Bashのデフォルト動作では、未定義の変数は空文字列として展開されます。これが原因で発生する典型的な事故を見てみましょう。

#!/bin/bash # 危険なスクリプトの例(set -u なし) TARGET_DIR="$TARGETT_DIR" # スペルミス:TARGETT_DIR は未定義 rm -rf "$TARGET_DIR/" # 展開結果: rm -rf / → 壊滅的被害

set -u があれば、未定義変数を参照した時点でスクリプトが停止します。

$ bash test.sh test.sh: line 2: TARGETT_DIR: unbound variable

8. デフォルト値の指定と set -u の共存

set -u を有効にした状態で変数にデフォルト値を設定したい場合は、${変数名:-デフォルト値} という書き方を使います。

#!/bin/bash set -u # LOG_LEVEL が未設定の場合は "INFO" を使う LOG_LEVEL="${LOG_LEVEL:-INFO}" # BACKUP_DIR が未設定の場合は /tmp/backup を使う BACKUP_DIR="${BACKUP_DIR:-/tmp/backup}" echo "ログレベル: $LOG_LEVEL" echo "バックアップ先: $BACKUP_DIR"

3つの組み合わせ:set -eux が現場の定番

9. set -eux を一行で書く

実務のシェルスクリプトでは、set -eset -uset -x の3つを組み合わせた set -eux(または set -euxo pipefail)がスクリプト冒頭の定番になっています。

#!/bin/bash set -eux # e: エラーで即終了 # u: 未定義変数をエラー # x: コマンドをトレース表示 DEPLOY_DIR="/var/www/html" BACKUP_DIR="/var/www/backup" mkdir -p "$BACKUP_DIR" cp -r "$DEPLOY_DIR" "$BACKUP_DIR/$(date +%Y%m%d)" echo "デプロイバックアップ完了"

10. pipefail との組み合わせ

パイプを使うコマンドでは set -e だけでは不十分な場合があります。パイプの途中でエラーが起きても、最後のコマンドが成功すれば全体の終了コードが0になってしまうためです。

set -o pipefail を追加することで、パイプの途中のコマンドが失敗した場合にも終了コードにエラーが伝播します。

#!/bin/bash set -euxo pipefail # パイプ途中の失敗も検知できる grep "ERROR" /var/log/app.log | wc -l

実際の運用スクリプトでは set -euxo pipefail を1行目に書くのが最も安全なスタートラインです。

デバッグ出力の活用と実務Tips

11. BASH_XTRACEFD でトレースをファイルへ書き出す

set -x のトレース出力はデフォルトで標準エラー出力(stderr)へ出力されます。本番環境でトレースを有効にしたままログファイルに保存したい場合は BASH_XTRACEFD を使います。

#!/bin/bash # トレース出力をファイルディスクリプタ3に割り当て exec 3>/tmp/debug_trace.log BASH_XTRACEFD=3 set -x BACKUP_DIR="/tmp/backup" mkdir -p "$BACKUP_DIR" cp /etc/hosts "$BACKUP_DIR/" set +x exec 3>&- # ファイルディスクリプタを閉じる

このようにすると、通常の stdout/stderr は画面に表示されたままで、トレースだけ /tmp/debug_trace.log に書き出されます。cron で実行する自動化スクリプトのデバッグに特に有効です。

12. PS4 でトレースの見やすさを改善する

デフォルトのトレースプレフィックスは + ですが、PS4 環境変数を変更することで行番号やタイムスタンプを追加できます。

#!/bin/bash # 行番号付きのトレース表示 export PS4='+(${BASH_SOURCE}:${LINENO}): ' set -x echo "処理開始" sleep 1 echo "処理終了"

# 実行結果 +(debug_test.sh:5): echo '処理開始' 処理開始 +(debug_test.sh:6): sleep 1 +(debug_test.sh:7): echo '処理終了' 処理終了

行番号が表示されるため、エラーが発生した行を即座に特定できます。

トラブルシュート|よくあるエラーと対処法

「unbound variable」エラーへの対処

set -u を有効にすると unbound variable エラーが出やすくなります。多くの場合、スペルミスや初期化忘れが原因です。

# エラー例 ./script.sh: line 5: MY_VARR: unbound variable # 対処:変数名を確認し、デフォルト値を設定する MY_VAR="${MY_VAR:-default_value}"

set -e で意図せずスクリプトが終了する場合

grep が一致なしで終了コード1を返す場合や、test コマンドが偽で終了する場合など、意図した「失敗」でスクリプトが止まることがあります。

#!/bin/bash set -e # grep が0件のとき終了コード1を返し、set -e でスクリプト終了してしまう # 対処:|| true を付けて終了コードを無視する WARN_COUNT=$(grep -c "WARN" /var/log/app.log || true) echo "警告件数: $WARN_COUNT"

サブシェルでのデバッグ

$()|(パイプ)はサブシェルで実行されます。set -x はサブシェルにも引き継がれますが、set -e の動作が予想外になる場合があります。Linux 基本コマンドの解説も合わせて確認すると理解が深まります。

#!/bin/bash set -euxo pipefail # サブシェルで実行されるコマンドも set -e の対象 FILES=$(ls /nonexistent 2>/dev/null || echo "") if [ -z "$FILES" ]; then echo "ファイルなし" fi

本記事のまとめ

やりたいこと コマンド
実行コマンドを全てトレース表示する set -x
トレースを無効化する set +x
エラー発生時に即終了させる set -e
未定義変数をエラーにする set -u
パイプ途中のエラーを検知する set -o pipefail
3つをまとめて設定する(現場の定番) set -euxo pipefail
スクリプト実行時にトレースを有効にする bash -x スクリプト名.sh
行番号付きトレースを表示する PS4='+(${BASH_SOURCE}:${LINENO}): '
トレースをファイルへ書き出す BASH_XTRACEFD=3
シェルスクリプトのデバッグは「エラーを早期に発見して、被害を最小化する」という発想が基本です。set -euxo pipefail の4行を冒頭に書く習慣を持つだけで、スクリプトの品質は大幅に上がります。

特に本番環境の自動化スクリプトでは、エラーが握り潰される状態は最も危険です。デバッグオプションを使いこなして、安全で信頼性の高いスクリプトを書いてください。

スクリプトのバグ原因を特定できず、本番環境での作業に不安を感じていませんか?

シェルスクリプトのデバッグ技術は、サーバー運用自動化の質を左右します。set -x の使い方を知っているだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として15年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。