Shell 腳本中的 trap 信號處理
在 Linux 系統管理與自動化腳本開發中,優雅地處理異常與資源釋放是專業腳本與新手腳本的重要分水嶺。當我們執行一個長時間運行的批次任務時,如果使用者突然按下 Ctrl+C 終止腳本,或是系統發送關機信號,腳本中的臨時檔案(Temporary Files)往往來不及清理,導致磁碟空間浪費或後續腳本執行衝突。
這時,trap 命令就扮演了關鍵角色。它允許我們註冊特定的信號處理器(Signal Handler),在腳本接收到指定信號時執行預定義的清理動作。本文將深入探討 trap 的用法、常見信號及其在 Ubuntu 22.04 / Debian 12 環境下的實際應用。
什麼是 trap?
trap 是 Bash Shell 內建指令,用於指定當腳本接收到特定信號時應執行的命令。其基本語法如下:
trap 'commands' signal-list
其中,signal-list 可以是信號名稱(如 INT、TERM)或信號編號(如 2、15)。常見的信號包括:
- SIGINT (2):中斷信號,通常由
Ctrl+C觸發。 - SIGTERM (15):終止信號,通常由
kill指令或系統關機時發送,這是預設的優雅終止信號。 - EXIT (0):非信號,而是特殊保留字,腳本退出時(無論是否正常)都會觸發。
實戰範例:建立安全的臨時檔案清理機制
為了展示 trap 的實用性,我們撰寫一個腳本,模擬生成臨時檔案的過程,並在腳本結束時自動清理。
請建立檔案 cleanup_demo.sh 並填入以下內容:
#!/bin/bash
# 設定腳本結束時執行的清理函數
cleanup() {
if [[ -f "$TEMP_FILE" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 清理臨時檔案: $TEMP_FILE"
rm -f "$TEMP_FILE"
fi
# 確保即使發生錯誤也能繼續執行後續清理
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 腳本結束,執行殘餘清理工作..."
}
# 註冊 EXIT 信號,確保腳本退出時一定執行 cleanup
trap cleanup EXIT
# 設定臨時檔案路徑
TEMP_FILE=$(mktemp /tmp/my_script.XXXXXX)
# 模擬一些工作
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 正在處理資料..."
echo "臨時檔案位置: $TEMP_FILE"
# 寫入一些測試資料
for i in {1..5}; do
echo "Data chunk $i" >> "$TEMP_FILE"
sleep 1
done
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 工作完成!"
# 這裡故意留一個註解,模擬可能發生的錯誤處理邏輯
# if [[ $? -ne 0 ]]; then
# echo "發生錯誤"
# fi
exit 0
執行此腳本:
chmod +x cleanup_demo.sh
./cleanup_demo.sh
你會發現,無論腳本是正常執行完畢,還是你中途按下 Ctrl+C,最後都會看到清理臨時檔案的訊息。這證明了 trap ... EXIT 的強大之處:它確保了資源釋放邏輯的絕對執行。
處理特定信號:SIGINT 與 SIGTERM
有時我們不希望腳本被強制終止時立即消失,而是希望給使用者一個緩衝期,或記錄日誌。我們可以分別處理 SIGINT 和 SIGTERM。
#!/bin/bash
# 定義信號處理函數
handle_signal() {
local sig=$1
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 收到信號 $sig,正在安全退出..."
# 在這裡可以加入更複雜的邏輯,例如發送通知、保存狀態等
exit 1
}
# 同時監聽 SIGINT (2) 和 SIGTERM (15)
trap 'handle_signal SIGINT' INT
trap 'handle_signal SIGTERM' TERM
echo "腳本正在運行中,請嘗試按下 Ctrl+C 或執行 kill -TERM <PID>..."
# 模擬長時間運行
while true; do
sleep 5
done
常見問題與注意事項
1. trap 在管道(Pipeline)中的行為
在 Bash 中,trap 僅對當前 Shell 實例有效。如果你將腳本放入背景執行或管道中,信號處理可能會失效。例如:
# 錯誤示範:在子殼層中,trap 可能無法按預期工作
echo "test" | while read line; do
trap 'echo "cleanup"' EXIT
echo "$line"
done
建議將需要保護的邏輯封裝在函數中,並在主 Shell 層級註冊 trap。
2. 信號的優先級與覆蓋
當多個 trap 註冊了同一信號時,後註冊的會覆蓋先前的。此外,EXIT 信號的優先級極高,幾乎在所有退出路徑中都會觸發。若同時註冊 EXIT 和 SIGTERM,當腳本收到 SIGTERM 時,Bash 會先執行 SIGTERM 的處理器,若該處理器中調用 exit,則會觸發 EXIT 處理器。設計時需避免無限遞迴或衝突。
小結
trap 是撰寫健壯 Shell 腳本不可或缺的工具。透過正確註冊 EXIT、SIGINT 或 SIGTERM 信號,我們可以確保資源釋放、日誌記錄與狀態保存等關鍵動作不會因意外終止而遺漏。在 Ubuntu 22.04 與 Debian 12 等現代 Linux 發行版中,Bash 的 trap 行為穩定且強大,建議所有系統管理員與開發者都養成在腳本開頭註冊清理函數的良好習慣。