Shell 腳本錯誤處理的完整指南 文章首圖

Shell 腳本錯誤處理的完整指南

Shell 腳本錯誤處理的完整指南

在 Linux 系統管理與自動化工作中,Shell 腳本是最強大的工具之一。然而,許多初學者常犯的一個錯誤是假設腳本中的每個指令都會順利執行。事實上,一個未預期的錯誤(例如檔案不存在、權限不足或網路中斷)若未被妥善處理,可能會導致資料損毀或系統狀態不一致。

這篇文章將帶你掌握 Shell 腳本中錯誤處理的核心技巧,從基礎的 set 選項到高階的函數化錯誤處理,幫助你編寫出更健壯(Robust)的腳本。

為什麼需要錯誤處理?

想像一下,你的腳本正在備份資料庫,如果 mysqldump 失敗了,但腳本繼續執行並刪除了原始資料,這將是災難性的。錯誤處理的目的不在於阻止錯誤發生,而在於快速失敗(Fail-Fast)明確回報以及確保環境清潔

1. 基礎防禦:使用 set 選項

在腳本的最開頭加入 set 選項是建立防禦體系的第一步。

  • set -e:這是「快速失敗」模式。一旦任何指令回傳非零的退出狀態(即發生錯誤),腳本會立即中止。這是防止錯誤連鎖反應的關鍵。
  • set -u:檢查未定義的變數。如果你引用了 $MY_VAR 但忘記定義它,腳本會報錯而非傳回空字串,這能避免許多隱蔽的 Bug。
  • set -o pipefail:這對於管道(Pipeline)至關重要。預設情況下,cmd1 | cmd2 的回傳值取決於 cmd2。啟用後,若管道中任何一個指令失敗,整體回傳值即為失敗。
#!/bin/bash
# 啟用嚴格模式
set -euo pipefail

# 測試:若此檔案不存在,腳本會立即停止
if [[ ! -f "/etc/non_existent_file" ]]; then
    echo "檔案不存在,觸發錯誤處理" >&2
    exit 1
fi

echo "腳本執行成功"

2. 客製化錯誤處理函數

雖然 set -e 很有用,但它無法處理所有情境,例如在 if 條件判斷或 while 迴圈中。我們需要一個統一的錯誤回報機制。

#!/bin/bash
set -euo pipefail

# 定義錯誤處理函數
handle_error() {
    local exit_code=$?
    local line_number=$1
    echo "❌ 錯誤發生於第 ${line_number} 行,退出碼: ${exit_code}" >&2
    # 這裡可以加入清理動作,例如刪除暫存檔
    cleanup
    exit ${exit_code}
}

# 定義清理函數
cleanup() {
    echo "🧹 執行清理程序..."
    rm -f /tmp/temp_backup.dat
}

# 使用 trap 追蹤錯誤
trap 'handle_error $LINENO' ERR

# 模擬一個會失敗的指令
false

echo "這行不會被執行"

在上述範例中,trap 'handle_error $LINENO' ERR 告訴 Shell:當任何指令失敗時,執行 handle_error 函數,並傳入當前的行號。

3. 處理特定指令的失敗

有些時候,我們預期某些指令可能會失敗,但不想讓腳本終止。這時可以使用 || 運算子或 if 判斷式。

#!/bin/bash
set -euo pipefail

# 情境:嘗試刪除檔案,若檔案不存在則忽略錯誤
if ! rm -f /tmp/nonexistent_file; then
    echo "警告:檔案不存在或無法刪除,已忽略此錯誤"
fi

# 情境:使用 || 提供預設值
DOMAIN="google.com"
# 若 ping 失敗,回傳 "offline"
STATUS=$(ping -c 1 "$DOMAIN" > /dev/null 2>&1 && echo "online" || echo "offline")
echo "狀態: ${STATUS}"

常見問題與解決方案

Q1: set -eif 判斷式中無效? 是的,這是常見的誤解。set -e 僅在指令作為獨立命令執行時生效。在 if&&||! 後面的指令,其失敗不會觸發退出。

  • 解決方法:使用 trap 來監聽 ERR 事件,或在 if 內部明確檢查 $?

Q2: 如何同時處理多個錯誤來源? 當腳本複雜時,單一的全局 trap 可能不夠靈活。

  • 解決方法:將錯誤處理邏輯封裝成函數,並在關鍵步驟手動呼叫。例如:
    run_safe() {
        "$@" || { echo "指令 $1 失敗"; exit 1; }
    }
    run_safe cp /src /dst

小結

撰寫健壯的 Shell 腳本並非僅靠經驗,而是依賴嚴謹的錯誤處理機制。記住以下黃金法則:

  1. 始終在腳本開頭使用 set -euo pipefail
  2. 善用 trap 來集中處理錯誤與清理資源。
  3. 區分「預期內的錯誤」(如檔案不存在)與「意外錯誤」(如權限不足),並分別處理。

透過這些技巧,你的腳本將不再只是「能跑就好」,而是成為可靠、易於除錯的專業工具。現在,試著將這些原則套用到你日常的腳本中吧!