Contents

Shell Script 錯誤中斷方法

在 Shell Script 中,預設情況下某個指令執行失敗(回傳非零的 exit code),腳本不會自動停止,而是繼續執行後面的指令。這常常導致後續操作在錯誤的狀態下繼續執行,產生難以追蹤的問題。

預設行為的問題

1
2
3
4
#!/bin/bash
cp important_file.txt /backup/
rm -rf /some/directory   # 即使 cp 失敗,這行還是會執行
echo "Done"

如果 cp 失敗了,腳本仍然繼續執行 rm -rf,可能造成嚴重後果。

set -e:遇到錯誤立即退出

加上 set -e(等同於 set -o errexit)後,任何指令回傳非零 exit code,腳本就會立即停止執行:

1
2
3
4
5
6
#!/bin/bash
set -e

cp important_file.txt /backup/  # 若失敗,腳本在此停止
rm -rf /some/directory           # 不會被執行
echo "Done"

set -e 的限制

set -e 並非萬能,以下情況不會觸發退出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
set -e

# if 條件中的指令失敗不會觸發 set -e
if false; then
    echo "not reached"
fi

# && 或 || 連結的指令
false || true   # 不會退出

# 被 ! 否定的指令
! false         # 不會退出

set -u:未定義變數視為錯誤

預設情況下,使用未定義的變數會得到空字串,set -u(等同於 set -o nounset)讓這種情況直接報錯:

1
2
3
4
5
#!/bin/bash
set -u

echo $UNDEFINED_VAR  # 沒有 set -u:印出空字串
                     # 有 set -u:報錯並停止

set -o pipefail:Pipe 中任一失敗就停止

set -e 只看最後一個指令的 exit code,在 pipe 中間失敗的指令不會觸發:

1
2
3
4
set -e

# 即使 cat nonexistent_file 失敗,因為 grep 成功,整行 exit code 為 0
cat nonexistent_file | grep "pattern"

加上 set -o pipefail 後,pipe 中任何一個指令失敗,整個 pipe 就算失敗:

1
2
3
4
set -e
set -o pipefail

cat nonexistent_file | grep "pattern"  # 現在會因 cat 失敗而停止

完整的錯誤處理設定

1
2
3
4
5
#!/bin/bash
set -euo pipefail
# -e: 遇到錯誤立即退出
# -u: 未定義變數視為錯誤
# -o pipefail: pipe 中任一失敗就停止

trap ERR:錯誤時執行清理動作

trap 可以在腳本因錯誤退出時,執行指定的函式或指令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/bin/bash
set -euo pipefail

cleanup() {
    local exit_code=$?
    echo "腳本在第 $LINENO 行發生錯誤,exit code: $exit_code"
    rm -f /tmp/temp_file.txt
}

trap cleanup ERR

echo "開始執行..."
cp important_file.txt /backup/
echo "完成!"

允許特定指令失敗

1
2
3
4
5
6
7
8
9
set -e

# 方法一:加上 || true
grep "pattern" file.txt || true

# 方法二:在 if 中使用
if ! some_command_that_might_fail; then
    echo "指令失敗,但這是預期的"
fi

參考資料