bashのcallerコマンドでシェルスクリプトのコールスタックを記録する方法|エラー発生箇所と呼び出し元ログの設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips, シェルスクリプト > bashのcallerコマンドでシェルスクリプトのコールスタックを記録する方法|エラー発生箇所と呼び出し元ログの設計
「スクリプトがエラーで落ちた。ログには行番号があるが、どの関数がどの関数を呼んでそこに至ったかわからない」
本番環境でこの状況に直面すると、デバッグに余計な時間を取られます。

bashには caller という組み込みコマンドがあります。これを使うと、現在の関数が「どこから呼ばれたか」を行番号・ファイル名・関数名とともに取得できます。エラーハンドラに組み込むと、Pythonのトレースバックに相当するコールスタックをシェルスクリプトでも出力できます。

この記事では、RHEL 9.4 / Ubuntu 24.04 LTSの実環境で動作確認した例をもとに、caller コマンドの基本構文から trap ERR と組み合わせたコールスタックログの設計パターンまでを解説します。

この記事のポイント

・caller は「この関数を呼んだ行番号・ファイル名・関数名」を返すbash組み込みコマンド
・caller 0 で直接の呼び出し元、caller N で N 段上の呼び出し元を取得できる
・while ループで caller を繰り返すと完全なコールスタックが生成できる
・trap ERR と組み合わせると、エラー発生時のスタックを自動記録できる


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

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

RHEL 9.4 での実行結果:

[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

「11行目の 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 "$@"

設定ファイルが存在しない場合の実行結果(RHEL 9.4 サーバー sv01):

[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' ERRstack_trace 関数を組み合わせる
複数スクリプトで共通化する ライブラリファイルを作成し source /opt/lib/error_handler.sh で読み込む

caller を使うと「どこでエラーが起きたか」だけでなく「どのような経路でそこに至ったか」まで記録できます。本番環境の障害対応では、このコールスタックの有無でデバッグにかかる時間が大きく変わります。trap ERR と組み合わせてエラーハンドラをライブラリ化しておくと、今後書くすべてのスクリプトに自動的にこの仕組みが入ります。まず1本のスクリプトに組み込んで、動作を体感してみてください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

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

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

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

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

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

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

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

この記事を書いた人

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

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

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