本番環境でこの状況に直面すると、デバッグに余計な時間を取られます。
bashには
caller という組み込みコマンドがあります。これを使うと、現在の関数が「どこから呼ばれたか」を行番号・ファイル名・関数名とともに取得できます。エラーハンドラに組み込むと、Pythonのトレースバックに相当するコールスタックをシェルスクリプトでも出力できます。この記事では、RHEL 9.4 / Ubuntu 24.04 LTSの実環境で動作確認した例をもとに、
caller コマンドの基本構文から trap ERR と組み合わせたコールスタックログの設計パターンまでを解説します。この記事のポイント
・caller は「この関数を呼んだ行番号・ファイル名・関数名」を返すbash組み込みコマンド
・caller 0 で直接の呼び出し元、caller N で N 段上の呼び出し元を取得できる
・while ループで caller を繰り返すと完全なコールスタックが生成できる
・trap ERR と組み合わせると、エラー発生時のスタックを自動記録できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
callerコマンドとは(bashのコールスタック取得機能)
caller はbashの組み込みコマンドで、現在実行中の関数がどこから呼ばれたかを返します。関数の内部でのみ有効で、スクリプトのトップレベル(関数の外)では出力なしで終了コード 1 を返します。構文は次のとおりです。
# 基本構文 caller [N] # N を省略すると caller 0 と同じ(直接の呼び出し元) caller # 1段上の呼び出し元を取得 caller 1 # ループで全スタックを取得(スタックの底に達すると終了コード1を返す) i=0 while caller $i; do (( i++ )) done
行番号 関数名 ファイル名 の3要素です。関数の外から呼ばれた場合の関数名は main になります。callerの基本動作と出力を確認する
1. callerの出力を手元で確認する
次のスクリプトで動作を確認してみます。#!/bin/bash # /opt/scripts/caller_test.sh show_caller_info() { echo "=== caller の出力 ===" echo "caller 0(直接の呼び出し元): $(caller 0)" echo "caller 1(1段上の呼び出し元): $(caller 1)" } wrapper() { show_caller_info } wrapper
[admin@sv01 ~]$ bash /opt/scripts/caller_test.sh === caller の出力 === caller 0(直接の呼び出し元): 11 wrapper /opt/scripts/caller_test.sh caller 1(1段上の呼び出し元): 15 main /opt/scripts/caller_test.sh
wrapper 関数から呼ばれた」「15行目のトップレベル(main)から呼ばれた」という情報が取れています。2. FUNCNAME・BASH_SOURCE配列との関係
caller は「呼び出し元」の情報を返しますが、現在地の情報はbashの特殊配列が持っています。・FUNCNAME[0]:現在実行中の関数名
・FUNCNAME[1]:直接の呼び出し元の関数名
・BASH_SOURCE[0]:現在のファイル名
・BASH_SOURCE[1]:呼び出し元のファイル名
・LINENO:現在の行番号
caller N が返す情報は、FUNCNAME[N+1] と BASH_SOURCE[N+1] と対応しています。caller はスタックの底に達すると自動的に終了コード 1 を返すため、ループの終了条件として使いやすいという利点があります。実践:エラーログにコールスタックを記録する
caller の最も実用的な使い方が、trap ERR と組み合わせたエラーハンドラへの組み込みです。1. stack_trace関数とエラーハンドラの実装
#!/bin/bash # /opt/lib/error_handler.sh ← 共通ライブラリとして source で読み込む LOG_FILE="/var/log/myapp/error.log" # コールスタックを標準エラーとログに出力する関数 stack_trace() { local i=0 local frame echo "[CALL STACK]" >&2 # caller がスタックの底(終了コード1)を返すまでループ while frame=$(caller $i 2>/dev/null); do echo " #${i}: ${frame}" >&2 (( i++ )) done } # エラーハンドラ(trap ERR で呼ばれる) err_handler() { set +e # ハンドラ内部での再帰的なERR発火を防止 local exit_code=$? local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') { echo "[${timestamp}] [ERROR] exit_code=${exit_code}" stack_trace } | tee -a "${LOG_FILE}" >&2 exit "${exit_code}" } trap 'err_handler' ERR
2. 実際のエラー発生時の動作を確認する
上記のライブラリを読み込んだスクリプトを実行します。#!/bin/bash # /opt/scripts/deploy.sh set -euo pipefail source /opt/lib/error_handler.sh validate_config() { local config_file="$1" [[ -f "${config_file}" ]] || return 1 } deploy_app() { validate_config "/etc/myapp/production.conf" # デプロイ処理(省略) } main() { deploy_app } main "$@"
[admin@sv01 ~]$ bash /opt/scripts/deploy.sh [2026-08-17 14:31:05] [ERROR] exit_code=1 [CALL STACK] #0: 8 validate_config /opt/scripts/deploy.sh #1: 13 deploy_app /opt/scripts/deploy.sh #2: 18 main /opt/scripts/deploy.sh #3: 21 main /opt/scripts/deploy.sh
validate_config が 8行目で失敗し、それが deploy_app(13行目)→ main(18行目)→ トップレベル(21行目)という順序で呼ばれていたことが一目でわかります。これだけの情報があれば、ログを見た瞬間にデバッグ箇所を絞り込めます。3. 共通ライブラリとしてsourceで読み込む
エラーハンドラを共通ライブラリ化しておくと、全スクリプトで同じ品質のエラーログが得られます。#!/bin/bash # 各スクリプトの先頭でライブラリを読み込むだけでよい set -euo pipefail source /opt/lib/error_handler.sh # 以降は通常のスクリプトを書くだけ # エラーが発生すると自動的にコールスタックがログに残る
source で読み込むことで、trap ERR の設定を各スクリプトに毎回書く手間が省けます。シェルスクリプトの設計をさらに体系的に身につけたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で即使えるパターンを実機ハンズオンで習得できます。
callerを使う際の注意点とトラブルシュート
1. callerは関数の外では動作しない
スクリプトのトップレベル(関数の外)でcaller 0 を実行すると、出力なしで終了コード 1 を返します。while frame=$(caller $i 2>/dev/null) のように 2>/dev/null を使うことで、スタックの底でループが自然に終了します。なお、
2>/dev/null は「caller がスタックの底に達した」という想定済みの終了条件に使っています。通常のコマンドのエラーを握り潰すために 2>/dev/null や || true を使うことは禁じ手です。2. サブシェル内では独立したスタックになる
コマンド置換$() やパイプの中身はサブシェルで動くため、caller の情報は親シェルのスタックを引き継ぎません。err_handler をサブシェル内から呼ぶと、スタックが途中から始まる形になります。3. set -eとerr_handler内のコマンド失敗
err_handler 内で set -e が有効なまま別のコマンドが失敗すると、再帰的に trap ERR が発火してしまいます。ハンドラの先頭で set +e を実行することで、この問題を回避できます。4. Bash 3.x 環境での注意
RHEL 6(Bash 3.x)以前の環境ではcaller の動作が若干異なる場合があります。本記事の例はすべて RHEL 9.4(Bash 5.1)で動作確認しています。現在の主流環境(RHEL 8/9、Ubuntu 22.04/24.04)では問題なく利用できます。本記事のまとめ
| やりたいこと | 実装方法 |
|---|---|
| 直接の呼び出し元を取得する | caller 0(行番号・関数名・ファイル名を出力) |
| N段上の呼び出し元を取得する | caller N |
| 完全なコールスタックを生成する | i=0; while frame=$(caller $i 2>/dev/null); do ループ |
| エラー時にスタックを自動記録する | trap 'err_handler' ERR と stack_trace 関数を組み合わせる |
| 複数スクリプトで共通化する | ライブラリファイルを作成し source /opt/lib/error_handler.sh で読み込む |
caller を使うと「どこでエラーが起きたか」だけでなく「どのような経路でそこに至ったか」まで記録できます。本番環境の障害対応では、このコールスタックの有無でデバッグにかかる時間が大きく変わります。trap ERR と組み合わせてエラーハンドラをライブラリ化しておくと、今後書くすべてのスクリプトに自動的にこの仕組みが入ります。まず1本のスクリプトに組み込んで、動作を体感してみてください。
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:mpstatコマンドでLinuxのCPUコア別使用率を確認する方法|高負荷コアの特定とsysstatインストールも
- この記事の属するカテゴリ:Linuxtips・シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます