はじめに
Shellスクリプトで「途中で止められても大丈夫」なプログラムを作るには、trapコマンドを使用する。
trapを使うことで、スクリプトが終了するときやシグナルを受け取ったときに、自動的にクリーンアップ処理を実行できる。
trapとは
trapは、シェルがシグナルを受け取ったときや特定の条件で実行されるコマンドを指定するShellの組み込みコマンドである。
基本構文
trap 'コマンド' シグナルシグナル一覧
| シグナル | 数値 | 意味 | 発生タイミング |
|---|---|---|---|
| EXIT | - | スクリプト終了時 | 正常終了・異常終了時 |
| INT | 2 | 割り込み | Ctrl+C押下時 |
| TERM | 15 | 終了要求 | killコマンド実行時 |
| HUP | 1 | ハングアップ | ターミナル切断時 |
| QUIT | 3 | 終了(コアダンプ) | Ctrl+\押下時 |
| USR1 | 10 | ユーザー定義シグナル1 | プログラムで自由に使用 |
| USR2 | 12 | ユーザー定義シグナル2 | プログラムで自由に使用 |
trapを使ってみる
まずは基本的なtrapの使用例から始めてみる。
以下のサンプルスクリプトで実際の動作を確認してみることとする。
サンプルスクリプト:基本的なtrap
#!/bin/bash
# ファイル名: basic_trap.sh
echo "=== trapの基本動作テスト ==="
echo "スクリプトのPID: $$"
# クリーンアップ関数の定義
cleanup() {
# 二重実行を防ぐフラグ
if [ "${CLEANUP_DONE:-}" = "true" ]; then
return 0
fi
CLEANUP_DONE=true
echo
echo "クリーンアップ処理が実行されました"
echo "一時ファイルを削除中..."
rm -f "/tmp/test_$$"
echo "クリーンアップ完了"
exit 0
}
# trapの設定
trap cleanup EXIT INT TERM
# 一時ファイルの作成
echo "一時ファイルを作成: /tmp/test_$$"
echo "テストデータ" > "/tmp/test_$$"
echo "10秒間処理中... (Ctrl+Cで中断してみてください)"
echo " または別のターミナルから: kill $$"
# メイン処理(10秒間のループ)
for i in {1..10}; do
echo "処理中... $i/10"
sleep 1
done
echo "正常終了"実行結果
1. 正常終了した場合
$ bash basic_trap.sh
=== trapの基本動作テスト ===
スクリプトのPID: 12345
一時ファイルを作成: /tmp/test_12345
10秒間処理中... (Ctrl+Cで中断してみてください)
または別のターミナルから: kill 12345
処理中... 1/10
処理中... 2/10
処理中... 3/10
処理中... 4/10
処理中... 5/10
処理中... 6/10
処理中... 7/10
処理中... 8/10
処理中... 9/10
処理中... 10/10
正常終了
クリーンアップ処理が実行されました
一時ファイルを削除中...
クリーンアップ完了2. Ctrl+Cで中断した場合
$ bash basic_trap.sh
=== trapの基本動作テスト ===
スクリプトのPID: 12346
一時ファイルを作成: /tmp/test_12346
10秒間処理中... (Ctrl+Cで中断してみてください)
または別のターミナルから: kill 12346
処理中... 1/10
処理中... 2/10
処理中... 3/10
^C
クリーンアップ処理が実行されました
一時ファイルを削除中...
クリーンアップ完了3. killコマンドで終了した場合
# 端末1
$ bash basic_trap.sh
=== trapの基本動作テスト ===
スクリプトのPID: 12347
一時ファイルを作成: /tmp/test_12347
10秒間処理中... (Ctrl+Cで中断してみてください)
または別のターミナルから: kill 12347
処理中... 1/10
処理中... 2/10
処理中... 3/10
Terminated
クリーンアップ処理が実行されました
一時ファイルを削除中...
クリーンアップ完了
# 端末2
$ kill 12347trapの動作フローチャート
重要なポイント
- EXITシグナル: 正常終了・異常終了に関わらず必ず実行される
- INTシグナル: Ctrl+C押下時に実行される
- TERMシグナル: killコマンド実行時に実行される
- $$変数: 現在のプロセスIDを取得(一時ファイル名のユニーク化に使用)
二重実行の問題と対策
trap cleanup EXIT INT TERM とすると、例えばCtrl+Cで中断した場合:
INTシグナルでcleanup実行- 直後に
EXITシグナルでcleanup再実行
この二重実行を防ぐため、上記のサンプルでは CLEANUP_DONE フラグを使用している。
使用例
1. クリーンアップ処理
#!/bin/bash
# 一時ファイルの定義
TEMP_FILE="/tmp/script_temp_$$"
# クリーンアップ関数
cleanup() {
# 二重実行を防ぐフラグ
if [ "${CLEANUP_DONE:-}" = "true" ]; then
return 0
fi
CLEANUP_DONE=true
echo "クリーンアップ処理を実行中..."
[ -f "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
echo "一時ファイルを削除しました"
exit 0
}
# trapの設定
trap cleanup EXIT INT TERM
# メイン処理
echo "処理開始: 一時ファイル $TEMP_FILE を作成"
echo "作業中のデータ" > "$TEMP_FILE"
echo "10秒間処理中... (Ctrl+Cで中断してみてください)"
sleep 10
echo "処理完了"2. プロセス監視とクリーンアップ
#!/bin/bash
# バックグラウンドプロセスのPIDを格納する変数
BG_PID=""
# クリーンアップ関数(詳細版)
cleanup() {
# 二重実行を防ぐフラグ
if [ "${CLEANUP_DONE:-}" = "true" ]; then
return 0
fi
CLEANUP_DONE=true
echo
echo "=== クリーンアップ処理開始 ==="
# バックグラウンドプロセスの終了
if [ -n "$BG_PID" ] && kill -0 "$BG_PID" 2>/dev/null; then
echo "バックグラウンドプロセス (PID: $BG_PID) を終了中..."
kill "$BG_PID"
wait "$BG_PID" 2>/dev/null
echo "バックグラウンドプロセスを終了しました"
fi
# 一時ディレクトリの削除
if [ -d "/tmp/work_$$" ]; then
echo "一時ディレクトリを削除中..."
rm -rf "/tmp/work_$$"
echo "一時ディレクトリを削除しました"
fi
echo "=== クリーンアップ処理完了 ==="
exit 0
}
# 複数のシグナルに対してtrapを設定
trap cleanup EXIT INT TERM HUP
# 作業ディレクトリの作成
mkdir -p "/tmp/work_$$"
# バックグラウンドで長時間処理を実行
(
while true; do
date >> "/tmp/work_$$/log.txt"
sleep 1
done
) &
# バックグラウンドプロセスのPIDを記録
BG_PID=$!
echo "バックグラウンドプロセス開始 (PID: $BG_PID)"
# メイン処理
echo "メイン処理実行中... (15秒間または Ctrl+C で中断)"
sleep 15
echo "処理完了"3. ログファイルのローテーション例
#!/bin/bash
LOG_FILE="/tmp/app.log"
OLD_LOG_FILE="/tmp/app.log.old"
# ログローテーション関数
rotate_log() {
echo "$(date): ログローテーション実行" >> "$LOG_FILE"
if [ -f "$LOG_FILE" ]; then
mv "$LOG_FILE" "$OLD_LOG_FILE"
echo "$(date): 新しいログファイル開始" >> "$LOG_FILE"
echo "ログファイルをローテーションしました"
fi
}
# USR1シグナルでログローテーション
trap rotate_log USR1
# 通常のクリーンアップ
trap 'echo "アプリケーション終了"; exit 0' EXIT INT TERM
echo "アプリケーション開始 (PID: $$)"
echo "ログローテーションは: kill -USR1 $$"
# メインループ
counter=0
while true; do
counter=$((counter + 1))
echo "$(date): ログエントリ #$counter" >> "$LOG_FILE"
echo "ログエントリ #$counter を記録"
sleep 2
donetrapの無効化と再設定
trapについて一度設定したものを無効化したり再設定したりできる。
以下ので例で試せる。
#!/bin/bash
# 最初のtrap設定
trap 'echo "最初のクリーンアップ"' EXIT
echo "最初のtrap設定完了"
# trapの確認(bash固有機能)
trap -p EXIT
# trapの無効化
trap - EXIT
echo "trapを無効化しました"
# 新しいtrapの設定
trap 'echo "新しいクリーンアップ"; rm -f /tmp/new_temp' EXIT
echo "新しいtrap設定完了"
# 現在のtrap設定を確認
echo "現在のtrap設定:"
trap -pシェル互換性について
重要: trap -p は bash 固有の機能で、POSIX準拠のシェル(sh、dash など)では使用できない。
# POSIX準拠シェルでのtrap確認方法
#!/bin/sh
trap 'echo "クリーンアップ"' EXIT
echo "trap設定完了"
# 引数なしのtrapで現在の設定を表示(POSIX互換)
trap実行結果例
bashで実行した場合
$ bash trap_management.sh
最初のtrap設定完了
trap -- 'echo "最初のクリーンアップ"' EXIT
trapを無効化しました
新しいtrap設定完了
現在のtrap設定:
trap -- 'echo "新しいクリーンアップ"; rm -f /tmp/new_temp' EXIT
新しいクリーンアップshで実行した場合
$ sh trap_management.sh
最初のtrap設定完了
trap_management.sh: 9: trap: Illegal option -p
trapを無効化しました
新しいtrap設定完了
現在のtrap設定:
trap_management.sh: 18: trap: Illegal option -p
最初のクリーンアップベストプラクティスについて
1. エラーハンドリングと組み合わせ
エラーハンドリングとして利用する。
#!/bin/bash
set -euo pipefail # エラー時即座に終了
cleanup() {
# 二重実行を防ぐフラグ
if [ "${CLEANUP_DONE:-}" = "true" ]; then
return 0
fi
CLEANUP_DONE=true
local exit_code=$?
echo "終了コード: $exit_code"
if [ $exit_code -ne 0 ]; then
echo "エラーが発生しました"
# エラー情報をログに記録
echo "$(date): エラー終了 (exit code: $exit_code)" >> /tmp/error.log
fi
# クリーンアップ処理
[ -f "/tmp/work_$$" ] && rm -f "/tmp/work_$$"
}
trap cleanup EXIT
# 危険な処理例
echo "重要な処理を実行中..."
false # エラーを発生させる例まとめ
trapの重要なポイント
- 必ず設定する: 長時間実行するスクリプトには必須
- 複数シグナル対応:
EXIT INT TERMは基本セット - 二重実行対策: フラグを使って重複実行を防ぐ
- 階層的クリーンアップ: 処理の段階に応じたクリーンアップ
- テスト重要:
Ctrl+Cで中断テストを必ず実行
使い分けガイド
# 簡単なクリーンアップ
trap 'rm -f /tmp/temp_file' EXIT
# 中断可能な処理
trap 'echo "中断されました"; exit 1' INT
# エラーハンドリング
trap 'cleanup_function' EXIT INT TERM HUPtrapを適切に使用することで、どんな状況でも安全に終了できる堅牢なShellスクリプトが作成できる。
参考
Bash Manual - Signals and Job Control
https://www.gnu.org/software/bash/manual/bash.html#SignalsPOSIX Shell Command Language
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.htmlAdvanced Bash-Scripting Guide - Debugging
https://tldp.org/LDP/abs/html/debugging.html#TRAPREF
環境
bash 5.x以上(trap -p オプション使用時)
POSIX準拠シェル(sh、dash など)注意: trap -p は bash 固有機能のため、POSIX準拠シェルでは引数なしの trap を使用する。
おわりに
最近シェルスクリプトを書くことが多いので、trapについて学んでみた。
簡単なスクリプトでもエラーハンドリングを考えると、trapを使わないといけない部分があるのでこのあたりは覚えておいたほうが良いだろう。
特に本番環境で動作するスクリプトや、重要なデータを扱うスクリプトでは必須の機能といえる。
まずは簡単なクリーンアップ処理から始めて、エラーハンドリングから複雑な処理も混ぜて学んでいこうと思う。