错误处理

脚本执行过程中难免出错,良好的错误处理能让脚本更健壮、更可靠。Shell 提供了退出状态码、条件判断、trap 等机制来处理错误。

退出状态码

每个命令执行后都会返回一个退出状态码,保存在 $? 变量中:

  • 0 表示成功
  • 非 0 表示失败
ls /etc/passwd
echo $?

ls /notexist
echo $?

运行结果:

0
2

常见退出码

退出码含义
0成功
1一般错误
2误用命令
126无法执行
127命令未找到
128退出参数无效
130Ctrl+C 终止

自定义退出码

#!/bin/bash

if [ $# -eq 0 ]; then
    echo "用法:$0 <参数>"
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "错误:文件 $1 不存在"
    exit 2
fi

exit 0

检测命令执行结果

if 判断

if mkdir /tmp/test; then
    echo "目录创建成功"
else
    echo "目录创建失败"
fi

&& 和 ||

mkdir /tmp/test && echo "成功" || echo "失败"

&& 前面成功才执行后面,|| 前面失败才执行后面。

检查链

command1 || exit 1
command2 || exit 1
command3 || exit 1

trap 命令

trap 可以捕获信号和错误,在脚本退出时执行清理操作。

捕获退出信号

#!/bin/bash

cleanup() {
    echo "清理临时文件..."
    rm -f /tmp/temp_$$.*
}

trap cleanup EXIT

echo "创建临时文件..."
touch /tmp/temp_$$_1
touch /tmp/temp_$$_2

# 脚本正常退出或出错都会执行 cleanup

捕获特定信号

trap 'echo "收到中断信号"; exit 1' INT
trap 'echo "收到终止信号"; exit 1' TERM

常用信号:

信号含义
EXIT脚本退出
INTCtrl+C
TERM终止信号
ERR命令出错(需要 set -E)
HUP挂起

捕获错误

#!/bin/bash

set -E
trap 'echo "错误发生在第 $LINENO 行"; exit 1' ERR

command_not_exist

set -E 让 trap ERR 生效。

错误处理函数

#!/bin/bash

error_exit() {
    echo "错误:$1" >&2
    exit "${2:-1}"
}

check_file() {
    if [ ! -f "$1" ]; then
        error_exit "文件 $1 不存在" 2
    fi
}

check_file "/etc/passwd"
check_file "/notexist"

忽略错误

有时候需要忽略某个命令的错误:

rm -f /tmp/temp 2>/dev/null || true

|| true 确保整个表达式的退出码是 0。

重试机制

retry() {
    local max_attempts=$1
    local delay=$2
    shift 2
    local cmd="$@"
    
    local attempt=1
    while [ $attempt -le $max_attempts ]; do
        if $cmd; then
            return 0
        fi
        echo "第 $attempt 次尝试失败,等待 ${delay}秒..."
        sleep $delay
        attempt=$((attempt + 1))
    done
    
    echo "重试 $max_attempts 次后仍然失败"
    return 1
}

retry 3 2 curl -s http://example.com

超时处理

timeout 10s command || echo "命令超时"

或者用脚本实现:

run_with_timeout() {
    local timeout=$1
    shift
    local cmd="$@"
    
    $cmd &
    local pid=$!
    
    (
        sleep $timeout
        if kill -0 $pid 2>/dev/null; then
            kill $pid
        fi
    ) &
    local watchdog=$!
    
    wait $pid
    local status=$?
    kill $watchdog 2>/dev/null
    
    return $status
}

实用示例

带错误处理的脚本模板

#!/bin/bash

set -euo pipefail

cleanup() {
    rm -f "$temp_file"
}

trap cleanup EXIT

temp_file=$(mktemp)

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

error() {
    log "错误:$*" >&2
    exit 1
}

main() {
    log "开始执行"
    
    # 检查参数
    [ $# -ge 1 ] || error "需要提供参数"
    
    # 执行操作
    some_command || error "命令执行失败"
    
    log "执行完成"
}

main "$@"

安全删除

safe_delete() {
    local file=$1
    
    [ -e "$file" ] || { echo "文件不存在"; return 1; }
    [ -w "$file" ] || { echo "无写权限"; return 1; }
    
    read -p "确定删除 $file?(y/n) " confirm
    [ "$confirm" = "y" ] || { echo "取消删除"; return 0; }
    
    rm -f "$file"
}

小结

  • $? 保存上一个命令的退出状态
  • 0 表示成功,非 0 表示失败
  • if&&|| 可以检测命令执行结果
  • trap 捕获信号和错误,用于清理资源
  • set -E 配合 trap ERR 捕获错误
  • 良好的错误处理让脚本更健壮