trapコマンドでbashスクリプトのシグナルを捕捉・処理する方法|一時ファイル削除やエラー終了処理の実践例も

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips, シェルスクリプト > trapコマンドでbashスクリプトのシグナルを捕捉・処理する方法|一時ファイル削除やエラー終了処理の実践例も
「スクリプトをCtrl+Cで止めたら、一時ファイルが残ったままになってしまった…」
そういった経験をしたサーバー管理者は多いはずです。

bashスクリプトで本番運用をしていると、途中終了・割り込み・エラー時のクリーンアップは避けて通れません。一時ファイルやロックファイルを作るスクリプトが不意に中断されると、次回実行時に「古いロックファイルが残っている」「tmpディレクトリが膨らんでいる」という問題が起きます。

この記事では、bashの組み込みコマンド trap を使ったシグナルの捕捉と後処理の方法を解説します。
一時ファイルの自動削除、エラー時の強制終了処理、複数シグナルへの対応まで、RHEL 9.4 / Ubuntu 24.04 LTS で動作確認した手順でお伝えします。

この記事のポイント

・trap はbash組み込みコマンドでシグナル受信時に任意の処理を実行できる
・trap "rm -f /tmp/$$.lock" EXIT で終了時に一時ファイルを自動削除できる
・INT/TERM/ERRの3シグナルを押さえれば実務の9割のケースに対応できる
・trap '' SIGINT でシグナルを無視し、重要処理を割り込みから守れる


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

trapコマンドとは何か

trap は bash の組み込みコマンドです。シグナルや特殊イベント(EXIT・ERR等)を受信したときに、あらかじめ登録しておいたコマンドを自動実行する仕組みです。
Linuxでは、Ctrl+Cを押すと SIGINT(シグナル番号2)kill コマンドの既定は SIGTERM(シグナル番号15) が送られます。何もしなければスクリプトはその場で終了しますが、trap を使えば「終了前に必ずこの処理を実行する」というフックを仕掛けられます。
シグナル名 番号 発生タイミング
SIGINT 2 Ctrl+C(キーボード割り込み)
SIGTERM 15 kill コマンドの既定
SIGHUP 1 ターミナル切断・nohup無効化
SIGKILL 9 強制終了(trap で捕捉不可)
EXIT スクリプト終了時(正常・異常ともに)
ERR コマンドが0以外の終了コードで終了した時
SIGKILL(kill -9)はtrapで捕捉できません。これだけは覚えておいてください。

trapの基本構文

# 基本構文 trap '実行するコマンド' シグナル名またはイベント名 # 複数コマンドは ; で区切るか、関数を呼び出す trap 'echo "中断しました"; exit 1' INT TERM # trapの登録解除(空文字でリセット) trap '' INT # シグナルを無視する(空文字) trap - INT # デフォルト動作に戻す(ハイフン)

シグナル名はSIGを省略して書くのが一般的です(SIGINT → INT)。番号指定(trap '...' 2)でも動作しますが、可読性のためにシグナル名を推奨します。

最も実用的なパターン: EXIT トラップで一時ファイルを削除する

1. 一時ファイルの安全な削除パターン

本番スクリプトで一時ファイルを扱うときの鉄板パターンです。

#!/bin/bash # 一時ファイルを変数で管理 TMPFILE=$(mktemp /tmp/myapp.XXXXXX) # EXIT トラップで確実に削除 cleanup() { echo "クリーンアップ中..." >&2 rm -f "$TMPFILE" } trap cleanup EXIT # 以降の処理(途中でCtrl+Cしても cleanup が呼ばれる) echo "データを処理中..." some_command > "$TMPFILE" cat "$TMPFILE"

ポイントは3つあります。
mktemp を使う: /tmp/myapp.$$ でも構いませんが、mktemp は競合しないユニークなファイル名を生成します
EXIT を使う: INT や TERM を個別に列挙するより、EXIT に登録すれば正常終了・異常終了・シグナル終了のすべてで呼ばれます
変数は関数の外で定義する: 関数内でローカル宣言すると、trap 登録前に変数が消えているケースがあります
実際にRHEL 9.4の環境で動作確認した出力です。

# スクリプトを実行し、途中でCtrl+Cした場合 $ bash cleanup_demo.sh データを処理中... ^C クリーンアップ中... $ ls /tmp/myapp.* ls: cannot access '/tmp/myapp.*': No such file or directory # ファイルが確実に削除されている

2. ロックファイルを使うスクリプトへの応用

cronで定期実行するスクリプトで二重起動を防ぐロックファイル方式に trap を組み合わせます。

#!/bin/bash LOCKFILE="/var/run/myapp.lock" # ロックファイルの存在チェック if [ -f "$LOCKFILE" ]; then echo "エラー: 別のプロセスが実行中です($LOCKFILE)" >&2 exit 1 fi # ロックファイルを作成し、EXIT で削除する touch "$LOCKFILE" trap 'rm -f "$LOCKFILE"; echo "ロック解除完了"' EXIT echo "処理開始: PID=$$" # ... 本処理 ... sleep 10 echo "処理完了"

cron 環境でこのパターンを使うと、スクリプトが何らかの理由で異常終了してもロックファイルが残りません。二重起動防止の実装として非常に安定しています。

ERRトラップでエラーをその場で検出する

3. set -e との組み合わせでエラー処理を強化する

ERR イベントは、コマンドが0以外の終了コードを返したときに発火します。set -e(エラーで即座に終了)と組み合わせると、エラーが発生した行番号を記録できます。

#!/bin/bash set -e # エラーで即終了 # ERR トラップでエラー行番号とコマンドを記録 error_handler() { local exit_code=$? local line_no=$1 echo "[ERROR] 行 ${line_no} でエラーが発生しました(終了コード: ${exit_code})" >&2 echo "[ERROR] コマンド: ${BASH_COMMAND}" >&2 } trap 'error_handler $LINENO' ERR # テスト: 存在しないディレクトリへ移動 cd /nonexistent_dir echo "ここには到達しない"

# 実行結果 $ bash error_demo.sh bash: cd: /nonexistent_dir: No such file or directory [ERROR] 行 12 でエラーが発生しました(終了コード: 1) [ERROR] コマンド: cd /nonexistent_dir

$LINENO は現在の行番号を保持するbash変数です。$BASH_COMMAND はtrapが呼ばれた直前に実行されたコマンド文字列を保持します。これらを組み合わせると、ログから問題箇所を素早く特定できます。

INT・TERM・EXITを組み合わせた堅牢なスクリプト例

4. 実務で使える完全なテンプレート

以下は、一時ファイル管理・ロックファイル・エラーログを組み合わせた実務レベルのテンプレートです。

#!/bin/bash set -eu # -e: エラーで終了 / -u: 未定義変数でエラー SCRIPT_NAME=$(basename "$0") LOCKFILE="/var/run/${SCRIPT_NAME%.sh}.lock" LOGFILE="/var/log/${SCRIPT_NAME%.sh}.log" TMPFILE=$(mktemp) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"; } cleanup() { local exit_code=$? rm -f "$TMPFILE" "$LOCKFILE" if [ "$exit_code" -ne 0 ]; then log "異常終了 (exit_code=${exit_code})" else log "正常終了" fi } trap cleanup EXIT error_handler() { log "ERR: 行 $1 / コマンド: ${BASH_COMMAND}" } trap 'error_handler $LINENO' ERR # 二重起動チェック if [ -f "$LOCKFILE" ]; then log "別プロセスが実行中のためスキップ" exit 0 fi touch "$LOCKFILE" log "処理開始" # --- ここに本処理を書く --- log "処理完了"

このテンプレートをベースにすれば、どのような定期実行スクリプトでも安全に運用できます。

シグナルを無視する方法

5. 重要処理を割り込みから保護する

データベースへの書き込みやファイルのコピー中に割り込まれると、データが壊れる場合があります。そういった「途中で止まってはいけない処理」では、シグナルを一時的に無視する方法が有効です。

#!/bin/bash echo "保護区間の前: Ctrl+C で中断できます" sleep 3 # シグナルを無視(空文字でtrap) trap '' INT TERM echo "保護区間開始: ここではCtrl+Cが無効です" sleep 5 # 重要な処理(DBバックアップ等) echo "保護区間終了" # デフォルト動作に戻す trap - INT TERM echo "ここからはCtrl+Cで中断できます" sleep 3

注意: SIGKILL(kill -9)は trap '' では防げません。また、長時間シグナルを無視するとシステム管理者がプロセスを制御できなくなります。無視する区間は必要最小限にとどめてください。

trapのトラブルシュート

「trapが呼ばれない」場合のチェックポイント

SIGKILL(kill -9)を使っていないか:
kill -9 はtrapを完全にバイパスします。送り側のスクリプトや手動操作で kill -9 が使われていないか確認してください。
サブシェルで実行していないか:
( trap ... ) のようにサブシェル内でtrapを登録しても、親シェルには影響しません。

# NG: サブシェル内のtrapは親シェルに影響しない ( trap 'echo "サブシェル"' EXIT ) # OK: 親シェルで直接trap trap 'echo "親シェル"' EXIT

set -e が予期しないコマンドで発火していないか:
grepdiff は一致なし・差異なしの場合に終了コード1を返します。set -e 環境では意図せず終了することがあります。

# NG: grep が0件マッチで終了コード1 → set -e でスクリプト終了 grep "pattern" file.txt # OK: || true で終了コードを0に揃える grep "pattern" file.txt || true

trapの現在の登録状況を確認するには:

# 現在のtrap設定を一覧表示 trap -p # 特定のシグナルだけ確認 trap -p EXIT INT TERM

trapコマンド早見表

やりたいこと コマンド
終了時に一時ファイルを削除する trap 'rm -f "$TMPFILE"' EXIT
Ctrl+C 時にメッセージを表示して終了 trap 'echo "中断しました"; exit 1' INT
エラー発生行を記録する trap 'echo "行 $LINENO: $BASH_COMMAND" >&2' ERR
SIGINTを無視して割り込みを防ぐ trap '' INT
デフォルト動作に戻す trap - INT TERM
現在のtrap設定を確認する trap -p
複数シグナルに同じ処理を設定する trap 'cleanup' EXIT INT TERM
シェルスクリプトの信頼性を上げるうえで、trap は欠かせないコマンドです。まずは「EXIT トラップで一時ファイルを削除する」パターンから導入してみてください。既存のスクリプトに数行追加するだけで、運用上の問題の大半を防げます。
bashスクリプトの引数処理については getoptsコマンドでbashスクリプトの引数を処理する方法 も参考にしてください。
cronで定期実行するスクリプトを運用する場合は、chkconfigとsystemctlの使い方 と合わせてサービス管理も確認しておきましょう。
プロセスへのシグナル送信については Linux ポート確認の全コマンド の関連でも触れている Linux 基本コマンドの解説 を参照してください。

「trap を使いこなして、安心して動かせるスクリプトを書きたい」なら

trap を使えば、スクリプトの信頼性は確実に上がります。
ネットの古い情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

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

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

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

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

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

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

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

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

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

この記事を書いた人

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

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

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