Shell 脚本中的错误处理和调试技巧对于编写健壮的脚本非常重要。
错误处理
退出状态码
bash# 检查命令执行状态 command if [ $? -eq 0 ]; then echo "Command succeeded" else echo "Command failed with exit code $?" fi # 使用 && 和 || 进行条件执行 command1 && command2 # command2 仅在 command1 成功时执行 command1 || command2 # command2 仅在 command1 失败时执行
set 命令选项
bash# set -e: 任何命令失败时立即退出 set -e command1 command2 # 如果 command1 失败,不会执行 # set -u: 使用未定义变量时报错 set -u echo $undefined_var # 报错并退出 # set -o pipefail: 管道中任何命令失败时返回失败状态 set -o pipefail command1 | command2 # 如果 command1 失败,整个管道失败 # 组合使用 set -euo pipefail
错误处理函数
bash# 错误处理函数 error_exit() { echo "Error: $1" >&2 exit 1 } # 检查文件是否存在 check_file() { if [ ! -f "$1" ]; then error_exit "File $1 not found" fi } # 使用函数 check_file "config.txt"
trap 命令
bash# 捕获退出信号 cleanup() { echo "Cleaning up..." rm -f /tmp/tempfile exit } # 捕获 EXIT 信号 trap cleanup EXIT # 捕获 INT 信号(Ctrl+C) trap cleanup INT # 捕获多个信号 trap cleanup EXIT INT TERM
调试技巧
调试模式
bash# 启用调试模式 set -x # 打印每个命令 set -v # 打印输入行 # 禁用调试模式 set +x set +v # 组合使用 set -xv command1 command2 set +xv
调试输出
bash# 使用 echo 输出调试信息 echo "Debug: variable = $variable" # 使用 printf 格式化输出 printf "Debug: %s = %s\n" "variable" "$variable" # 使用 >&2 输出到标准错误 echo "Debug info" >&2
调试函数
bash# 调试函数 debug() { if [ "$DEBUG" = "true" ]; then echo "[DEBUG] $*" >&2 fi } # 使用调试函数 DEBUG=true debug "Processing file: $filename"
变量追踪
bash# 显示变量值 echo "var1 = $var1" echo "var2 = $var2" # 使用 declare 显示变量信息 declare -p var1 declare -p var2 # 显示所有变量 declare -p # 显示所有函数 declare -f
实际应用示例
健壮的脚本模板
bash#!/bin/bash # 设置错误处理 set -euo pipefail # 定义错误处理函数 error_exit() { echo "Error: $1" >&2 exit 1 } # 定义清理函数 cleanup() { echo "Cleaning up..." [ -n "$tempfile" ] && rm -f "$tempfile" } # 设置陷阱 trap cleanup EXIT INT TERM # 定义调试函数 debug() { if [ "${DEBUG:-false}" = "true" ]; then echo "[DEBUG] $*" >&2 fi } # 主函数 main() { debug "Starting script" # 检查参数 if [ $# -lt 1 ]; then error_exit "Usage: $0 <filename>" fi local filename="$1" debug "Processing file: $filename" # 检查文件 if [ ! -f "$filename" ]; then error_exit "File not found: $filename" fi # 创建临时文件 tempfile=$(mktemp) || error_exit "Failed to create temp file" debug "Created temp file: $tempfile" # 处理文件 cp "$filename" "$tempfile" # 处理逻辑... debug "Script completed successfully" } # 执行主函数 main "$@"
带错误检查的文件操作
bash#!/bin/bash # 安全的文件复制 safe_copy() { local src="$1" local dst="$2" # 检查源文件 if [ ! -f "$src" ]; then echo "Error: Source file not found: $src" >&2 return 1 fi # 检查目标目录 local dst_dir=$(dirname "$dst") if [ ! -d "$dst_dir" ]; then echo "Error: Destination directory not found: $dst_dir" >&2 return 1 fi # 检查写权限 if [ ! -w "$dst_dir" ]; then echo "Error: No write permission for: $dst_dir" >&2 return 1 fi # 执行复制 if ! cp "$src" "$dst"; then echo "Error: Failed to copy $src to $dst" >&2 return 1 fi echo "Successfully copied $src to $dst" return 0 } # 使用函数 safe_copy "source.txt" "destination.txt" || exit 1
带重试的操作
bash#!/bin/bash # 带重试的函数 retry_command() { local max_attempts="$1" shift local command=("$@") local attempt=1 while [ $attempt -le $max_attempts ]; do echo "Attempt $attempt of $max_attempts" if "${command[@]}"; then echo "Command succeeded" return 0 fi attempt=$((attempt + 1)) sleep 2 done echo "Command failed after $max_attempts attempts" >&2 return 1 } # 使用函数 retry_command 3 curl -s http://example.com || exit 1
日志记录
bash#!/bin/bash # 日志函数 log() { local level="$1" shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a script.log } log_info() { log "INFO" "$@" } log_error() { log "ERROR" "$@" } log_debug() { if [ "${DEBUG:-false}" = "true" ]; then log "DEBUG" "$@" fi } # 使用日志函数 log_info "Script started" log_debug "Debug information" log_error "An error occurred"
调试最佳实践
- 使用 set -euo pipefail: 提高脚本的健壮性
- 使用 trap 进行清理: 确保资源被正确释放
- 使用调试函数: 便于控制调试输出
- 记录日志: 便于问题追踪和调试
- 检查命令状态: 使用 $? 或 if 语句
- 使用有意义的错误消息: 帮助快速定位问题
- 测试边界情况: 确保脚本在各种情况下都能正常工作
- 使用变量默认值:
${VAR:-default}防止未定义变量错误