錯誤處理
部分內容由 LLM 生成,尚未經過人工驗證。
Shell 腳本錯誤處理、除錯技巧與最佳實踐。
Exit Codes 與 $?
每個命令執行後都會返回退出碼(exit code):
0- 成功1-255- 失敗(不同數字代表不同錯誤)
# 檢查上一個命令的退出碼
ls /tmp
echo $? # 0(成功)
ls /non-existent
echo $? # 2(檔案不存在)自訂退出碼
#!/bin/bash
# 定義退出碼常數
readonly E_SUCCESS=0
readonly E_NOFILE=1
readonly E_PERMISSION=2
readonly E_INVALID_ARG=3
check_file() {
local file=$1
if [ $# -eq 0 ]; then
echo "Error: No file specified" >&2
exit $E_INVALID_ARG
fi
if [ ! -e "$file" ]; then
echo "Error: File not found: $file" >&2
exit $E_NOFILE
fi
if [ ! -r "$file" ]; then
echo "Error: Permission denied: $file" >&2
exit $E_PERMISSION
fi
exit $E_SUCCESS
}
check_file "$1"使用退出碼進行流程控制
# if 語句
if command; then
echo "Success"
else
echo "Failed"
fi
# && 和 ||
command1 && command2 # command2 只在 command1 成功時執行
command1 || command2 # command2 只在 command1 失敗時執行
# 鏈式操作
mkdir /tmp/test && cd /tmp/test && touch file.txt || echo "Failed"set -e 與 set -o pipefail
set -e
命令失敗時立即退出腳本:
#!/bin/bash
set -e
echo "Step 1"
false # 這會導致腳本退出
echo "Step 2" # 不會執行set -e 的例外情況
set -e
# 在條件判斷中不會退出
if false; then
echo "Won't print"
fi
# 在 while/until 條件中不會退出
while false; do
echo "Won't loop"
done
# 使用 || 或 && 時不會退出
false || echo "Handled"
# 在管線中只檢查最後一個命令
false | true # 不會退出set -o pipefail
檢查管線中所有命令的退出碼:
#!/bin/bash
set -e
set -o pipefail
# 沒有 pipefail
false | true # 不會退出(只檢查 true)
# 有 pipefail
false | true # 會退出(檢查 false)組合使用
#!/bin/bash
set -euo pipefail
# 安全的管線處理
cat file.txt | grep "pattern" | sort | uniq
# 任一命令失敗都會導致腳本退出trap 命令
trap 用於捕捉信號和錯誤,執行清理操作:
基本語法
trap 'commands' SIGNAL [SIGNAL...]常見信號
| 信號 | 說明 |
|---|---|
EXIT | 腳本退出時(正常或異常) |
ERR | 命令失敗時(需 set -e) |
INT | Ctrl+C(SIGINT) |
TERM | 終止信號(SIGTERM) |
HUP | 終端關閉(SIGHUP) |
清理臨時檔案
#!/bin/bash
set -e
tmpfile=$(mktemp)
# 設定 trap 確保臨時檔案被刪除
trap "rm -f '$tmpfile'" EXIT
# 使用臨時檔案
echo "data" > "$tmpfile"
# ... 處理 ...
# EXIT 時自動執行 rm -f '$tmpfile'多個清理操作
#!/bin/bash
cleanup() {
echo "Cleaning up..." >&2
rm -f /tmp/lockfile
kill $background_pid 2>/dev/null || true
# 其他清理操作
}
trap cleanup EXIT
# 腳本內容
touch /tmp/lockfile
some_command &
background_pid=$!
# ... 執行任務 ...捕捉錯誤
#!/bin/bash
set -e
error_handler() {
local line=$1
echo "Error occurred in script at line: $line" >&2
# 記錄錯誤、發送通知等
exit 1
}
trap 'error_handler $LINENO' ERR
# 腳本內容
command1
command2忽略信號
# 忽略 Ctrl+C
trap '' INT
# 睡眠期間無法被 Ctrl+C 中斷
sleep 10
# 恢復預設行為
trap - INT組合範例
#!/bin/bash
set -euo pipefail
# 全域變數
tmpdir=""
logfile="/var/log/script.log"
# 清理函式
cleanup() {
local exit_code=$?
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
echo "Script exited with code: $exit_code" >> "$logfile"
exit $exit_code
}
# 錯誤處理
error_handler() {
echo "Error at line $1" >&2
cleanup
}
# 設定 trap
trap cleanup EXIT
trap 'error_handler $LINENO' ERR
trap 'echo "Interrupted"; exit 130' INT
# 建立臨時目錄
tmpdir=$(mktemp -d)
# 腳本主要邏輯
echo "Working in $tmpdir"
# ...錯誤訊息輸出
使用 stderr
# 錯誤訊息應輸出到 stderr(檔案描述符 2)
echo "Error: File not found" >&2
# 或使用函式
error() {
echo "ERROR: $*" >&2
}
error "Something went wrong"訊息等級
# 定義訊息函式
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
info() {
echo "[INFO] $*"
}
warn() {
echo "[WARN] $*" >&2
}
error() {
echo "[ERROR] $*" >&2
}
fatal() {
echo "[FATAL] $*" >&2
exit 1
}
# 使用
info "Starting process..."
warn "Configuration file not found, using defaults"
error "Failed to connect to database"
fatal "Critical error, cannot continue"除錯技巧
set -x
輸出每個執行的命令(帶擴展):
#!/bin/bash
set -x # 啟用除錯輸出
name="Alice"
echo "Hello, $name"
# 輸出: + echo 'Hello, Alice'
# Hello, Alice
set +x # 禁用除錯輸出PS4 變數
自訂除錯輸出格式:
#!/bin/bash
# 預設 PS4='+ '
set -x
echo "test"
# 輸出: + echo test
# 自訂 PS4
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
echo "test"
# 輸出: +(script.sh:10): echo test條件除錯
#!/bin/bash
# 使用環境變數控制除錯
if [ "${DEBUG:-}" = "1" ]; then
set -x
fi
# 使用
# DEBUG=1 ./script.sh函式追蹤
#!/bin/bash
# 啟用函式追蹤
set -o functrace
trace() {
echo "Entering ${FUNCNAME[1]} (called from ${FUNCNAME[2]})" >&2
}
func1() {
trace
echo "In func1"
}
func2() {
trace
func1
}
func2使用 DEBUG trap
#!/bin/bash
# 每個命令執行前觸發
trap 'echo "Line $LINENO: $BASH_COMMAND"' DEBUG
x=1
y=2
z=$((x + y))
echo $z最佳實踐
1. 輸入驗證
validate_input() {
local input=$1
if [ -z "$input" ]; then
error "Input cannot be empty"
return 1
fi
if [[ ! $input =~ ^[0-9]+$ ]]; then
error "Input must be a number"
return 1
fi
return 0
}
read -p "Enter a number: " num
if validate_input "$num"; then
echo "Valid input: $num"
else
exit 1
fi2. 依賴檢查
check_dependencies() {
local missing=()
for cmd in "$@"; do
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
error "Missing dependencies: ${missing[*]}"
return 1
fi
}
check_dependencies git curl jq || exit 13. 完整的錯誤處理範例
#!/bin/bash
set -euo pipefail
# 常數定義
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# 錯誤處理
error_exit() {
echo "${SCRIPT_NAME}: error: $*" >&2
exit 1
}
# 清理
cleanup() {
local exit_code=$?
# 清理操作
exit $exit_code
}
trap cleanup EXIT
# 主程式
main() {
# 檢查參數
if [ $# -eq 0 ]; then
error_exit "No arguments provided"
fi
# 檢查依賴
command -v jq &> /dev/null || error_exit "jq is not installed"
# 執行任務
# ...
return 0
}
main "$@"