Shell脚本错误处理#
错误处理是Shell脚本开发中的重要组成部分,良好的错误处理可以提高脚本的健壮性和可维护性。本教程将详细介绍Shell中的各种错误处理技术,从基础的退出状态码检查到高级的调试和错误恢复策略。
入门#
基本错误检查#
在Shell中,每个命令执行后都会返回一个退出状态码(0-255),其中0表示成功,非0表示失败。
# 检查命令执行结果
ls -la
if [ $? -eq 0 ]; then
echo "命令执行成功"
else
echo "命令执行失败"
fi
# 直接在if语句中检查
if ls -la; then
echo "命令执行成功"
else
echo "命令执行失败"
fi
# 使用逻辑运算符
ls -la && echo "成功" || echo "失败"简单错误处理#
# 检查文件是否存在
if [ ! -f "file.txt" ]; then
echo "错误: 文件不存在"
exit 1
fi
# 检查目录是否存在
if [ ! -d "dir" ]; then
echo "错误: 目录不存在"
exit 1
fi
# 检查参数是否提供
if [ $# -eq 0 ]; then
echo "错误: 请提供参数"
exit 1
fi基本调试技巧#
# 使用 -x 选项启用调试模式
# 在脚本开头添加
#!/bin/bash -x
# 或者在执行时指定
bash -x script.sh
# 使用 -v 选项显示脚本内容
bash -v script.sh
# 使用 -n 选项检查语法错误
bash -n script.sh中级#
错误捕获和处理#
set 命令#
set命令可以控制Shell的行为,包括错误处理模式。
# 遇到错误时立即退出
set -e
# 遇到未定义变量时出错
set -u
# 管道中任何命令失败时整个管道失败
set -o pipefail
# 组合使用
set -euo pipefail
# 临时禁用错误检查
set +e
dangerous_command
set -e错误处理函数#
# 基本错误处理函数
error_exit() {
echo "错误: $1"
exit 1
}
# 使用错误处理函数
if [ ! -f "file.txt" ]; then
error_exit "文件不存在"
fi
# 带错误码的错误处理函数
error_exit() {
local message=$1
local code=${2:-1}
echo "错误: $message"
exit $code
}
# 使用带错误码的函数
if [ ! -d "dir" ]; then
error_exit "目录不存在" 2
fi日志记录#
# 基本日志函数
log_info() {
echo "[INFO] $1"
}
log_error() {
echo "[ERROR] $1" >&2
}
log_debug() {
if [ "$DEBUG" = "true" ]; then
echo "[DEBUG] $1"
fi
}
# 使用日志函数
log_info "开始执行脚本"
if [ ! -f "file.txt" ]; then
log_error "文件不存在"
exit 1
fi
log_debug "文件存在,继续执行"
log_info "脚本执行完成"错误恢复#
# 临时文件清理
cleanup() {
rm -f tempfile.txt
echo "清理完成"
}
# 注册退出时执行清理
trap cleanup EXIT
# 执行可能失败的操作
cp large_file.txt tempfile.txt
# 错误恢复示例
backup_file() {
local file=$1
local backup=${file}.bak
# 备份文件
if cp "$file" "$backup"; then
echo "备份成功: $backup"
return 0
else
echo "备份失败"
return 1
fi
}
# 使用备份进行恢复
if ! backup_file "important.txt"; then
echo "尝试使用现有备份恢复"
if [ -f "important.txt.bak" ]; then
cp "important.txt.bak" "important.txt"
echo "恢复成功"
else
echo "恢复失败: 无备份文件"
exit 1
fi
fi高级#
高级调试技巧#
断点调试#
# 添加断点
debug() {
echo "断点: $1"
read -p "按Enter继续..."
}
# 在脚本中使用断点
log_info "开始处理"
debug "处理前"
process_data
debug "处理后"
log_info "处理完成"详细调试日志#
# 详细调试函数
debug() {
if [ "$DEBUG" = "true" ]; then
local func=${FUNCNAME[1]:-main}
local line=${BASH_LINENO[0]}
echo "[DEBUG] $func:$line $1"
fi
}
# 使用详细调试
process_file() {
local file=$1
debug "开始处理文件: $file"
if [ ! -f "$file" ]; then
debug "文件不存在"
return 1
fi
debug "文件存在,大小: $(stat -c %s "$file")"
# 处理文件
debug "处理完成"
return 0
}
# 启用调试
DEBUG=true
process_file "data.txt"追踪执行流程#
# 追踪函数调用
trace() {
local func=${FUNCNAME[1]}
local args="$@"
echo "[TRACE] 调用 $func($args)"
}
# 示例函数
func1() {
trace "$@"
func2 "$@"
}
func2() {
trace "$@"
echo "执行中..."
}
# 调用函数
func1 "参数1" "参数2"错误处理策略#
防御性编程#
# 防御性编程示例
safe_mkdir() {
local dir=$1
# 检查参数
if [ -z "$dir" ]; then
echo "错误: 目录名不能为空"
return 1
fi
# 检查是否存在
if [ -d "$dir" ]; then
echo "警告: 目录已存在"
return 0
fi
# 尝试创建
if mkdir -p "$dir"; then
echo "成功: 创建目录 $dir"
return 0
else
echo "错误: 无法创建目录 $dir"
return 1
fi
}
# 使用安全函数
safe_mkdir "path/to/dir"事务处理#
# 简单事务处理
transaction() {
local temp_file=$(mktemp)
local success=0
# 操作1
if ! operation1 > "$temp_file"; then
echo "操作1失败"
success=1
fi
# 操作2
if [ $success -eq 0 ] && ! operation2 < "$temp_file"; then
echo "操作2失败"
success=1
fi
# 清理
rm -f "$temp_file"
# 提交或回滚
if [ $success -eq 0 ]; then
echo "事务成功"
return 0
else
echo "事务失败,已回滚"
return 1
fi
}
# 执行事务
transaction错误报告#
# 错误报告函数
report_error() {
local message=$1
local exit_code=${2:-1}
local script=${0##*/}
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
# 输出到控制台
echo "[$timestamp] ERROR: $message" >&2
# 记录到日志文件
echo "[$timestamp] ERROR: $message" >> "${script}.log"
# 退出
exit $exit_code
}
# 使用错误报告
if [ ! -f "config.txt" ]; then
report_error "配置文件不存在"
fi
# 带上下文的错误报告
report_error_with_context() {
local message=$1
local exit_code=${2:-1}
local script=${0##*/}
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
local line=${BASH_LINENO[0]}
local func=${FUNCNAME[1]}
local error_msg="[$timestamp] ERROR in $script:$line ($func): $message"
echo "$error_msg" >&2
echo "$error_msg" >> "${script}.log"
exit $exit_code
}
# 使用带上下文的错误报告
validate_input() {
local input=$1
if [ -z "$input" ]; then
report_error_with_context "输入为空"
fi
}
validate_input ""大师#
全面错误处理策略#
错误处理框架#
# 错误处理框架
# 配置
DEBUG=${DEBUG:-false}
LOG_FILE=${LOG_FILE:-"$(basename $0 .sh).log"}
# 日志函数
log() {
local level=$1
shift
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
local message="[$timestamp] [$level] $@"
# 输出到控制台
echo "$message"
# 记录到日志文件
echo "$message" >> "$LOG_FILE"
}
log_info() {
log "INFO" "$@"
}
log_error() {
log "ERROR" "$@"
}
log_debug() {
if [ "$DEBUG" = "true" ]; then
log "DEBUG" "$@"
fi
}
# 错误处理函数
error_exit() {
local message=$1
local code=${2:-1}
log_error "$message"
exit $code
}
# 检查函数
check_file() {
local file=$1
local description=${2:-"文件"}
log_debug "检查 $description: $file"
if [ ! -e "$file" ]; then
error_exit "$description 不存在: $file"
fi
if [ ! -f "$file" ]; then
error_exit "$description 不是文件: $file"
fi
if [ ! -r "$file" ]; then
error_exit "$description 不可读: $file"
fi
log_debug "$description 检查通过"
return 0
}
# 清理函数
cleanup() {
log_info "执行清理操作"
# 清理代码
rm -f temp_*.txt
log_info "清理完成"
}
# 注册信号处理
trap cleanup EXIT
# 主函数
main() {
log_info "开始执行脚本"
# 检查参数
if [ $# -eq 0 ]; then
error_exit "请提供参数"
fi
# 检查文件
check_file "$1" "输入文件"
# 处理逻辑
log_info "处理文件: $1"
# 处理代码
log_info "脚本执行完成"
return 0
}
# 执行主函数
main "$@"异常处理模拟#
# 异常处理模拟
try() {
local cmd="$@"
local errfile=$(mktemp)
# 执行命令并捕获错误
if $cmd 2>"$errfile"; then
rm -f "$errfile"
return 0
else
local error=$(cat "$errfile")
rm -f "$errfile"
echo "$error"
return 1
fi
}
catch() {
local error=$1
local handler=$2
if [ -n "$error" ]; then
$handler "$error"
return 1
fi
return 0
}
# 示例
my_handler() {
echo "捕获到错误: $1"
}
try ls non_existent_file
error=$?
catch "$error" my_handler
try echo "正常执行"
error=$?
catch "$error" my_handler高级调试技术#
交互式调试#
# 交互式调试函数
interactive_debug() {
local prompt="debug> "
local cmd
echo "进入调试模式,输入 'continue' 继续执行"
while true; do
read -p "$prompt" cmd
case $cmd in
continue)
echo "退出调试模式"
break
;;
quit|exit)
echo "退出脚本"
exit 1
;;
variables|vars)
set
;;
*)
# 执行任意命令
eval "$cmd"
;;
esac
done
}
# 在脚本中使用
process_data() {
echo "处理数据..."
# 处理代码
interactive_debug
echo "继续执行..."
}
process_data性能分析#
# 性能分析函数
profile() {
local func=$1
local args="$@"
shift
local start=$(date +%s.%N)
"$func" "$@"
local end=$(date +%s.%N)
local duration=$(echo "$end - $start" | bc)
echo "函数 $func 执行时间: $duration 秒"
}
# 示例函数
slow_function() {
echo "执行缓慢操作..."
sleep 1
echo "操作完成"
}
# 分析性能
profile slow_function内存使用分析#
# 内存使用分析
mem_usage() {
local pid=$$
local mem=$(ps -o rss= -p $pid)
echo "当前内存使用: $mem KB"
}
# 在关键点检查内存使用
start_operation() {
mem_usage
echo "开始操作..."
# 操作代码
mem_usage
echo "操作完成"
}
start_operation无敌#
高级错误处理系统#
错误管理框架#
#!/bin/bash
# 高级错误管理框架
# 配置
DEBUG=${DEBUG:-false}
VERBOSE=${VERBOSE:-false}
LOG_FILE=${LOG_FILE:-"$(basename $0 .sh).log"}
ERROR_FILE=${ERROR_FILE:-"$(basename $0 .sh).err"}
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log() {
local level=$1
shift
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
local message="[$timestamp] [$level] $@"
# 控制台输出
case $level in
ERROR) echo -e "${RED}$message${NC}" >&2 ;;
WARNING) echo -e "${YELLOW}$message${NC}" ;;
INFO) echo -e "${GREEN}$message${NC}" ;;
DEBUG) if [ "$DEBUG" = "true" ]; then echo -e "${BLUE}$message${NC}" ; fi ;;
*) echo "$message" ;;
esac
# 日志文件记录
echo "$message" >> "$LOG_FILE"
# 错误文件记录
if [ "$level" = "ERROR" ]; then
echo "$message" >> "$ERROR_FILE"
fi
}
# 错误处理函数
error() {
local message=$1
local code=${2:-1}
log "ERROR" "$message"
exit $code
}
# 警告函数
warning() {
log "WARNING" "$1"
}
# 信息函数
info() {
log "INFO" "$1"
}
# 调试函数
debug() {
log "DEBUG" "$1"
}
# 详细信息函数
verbose() {
if [ "$VERBOSE" = "true" ]; then
log "INFO" "$1"
fi
}
# 检查函数
check() {
local condition=$1
local message=$2
local code=${3:-1}
if [ ! $condition ]; then
error "$message" $code
fi
}
# 安全执行函数
safe_exec() {
local cmd="$1"
local description=${2:-"命令"}
debug "执行: $cmd"
if eval "$cmd"; then
verbose "$description 执行成功"
return 0
else
error "$description 执行失败"
fi
}
# 清理函数
cleanup() {
info "执行清理操作"
# 清理代码
rm -f temp_*.txt
info "清理完成"
}
# 注册信号处理
trap cleanup EXIT
# 注册错误处理
trap 'error "脚本意外退出"' ERR
# 主函数
main() {
info "开始执行脚本: $(basename $0)"
# 检查参数
check [ $# -ge 1 ] "请提供输入文件"
local input_file=$1
# 检查文件
check [ -f "$input_file" ] "输入文件不存在: $input_file"
check [ -r "$input_file" ] "输入文件不可读: $input_file"
info "处理文件: $input_file"
# 处理逻辑
safe_exec "cp "$input_file" "${input_file}.bak"" "备份文件"
safe_exec "sed -i 's/old/new/g' "$input_file"" "修改文件"
info "脚本执行完成"
return 0
}
# 执行主函数
main "$@"分布式错误处理#
# 分布式错误处理
# 节点错误处理
node_error() {
local node=$1
local error=$2
log "ERROR" "节点 $node: $error"
# 错误恢复逻辑
if [ "$error" = "timeout" ]; then
log "INFO" "尝试重新连接节点 $node"
# 重新连接代码
fi
}
# 执行分布式命令
distributed_exec() {
local nodes=($1)
local cmd=$2
local errors=0
for node in "${nodes[@]}"; do
log "INFO" "在节点 $node 执行: $cmd"
if ssh "$node" "$cmd"; then
log "INFO" "节点 $node 执行成功"
else
node_error "$node" "命令执行失败"
errors=$((errors + 1))
fi
done
if [ $errors -eq 0 ]; then
log "INFO" "所有节点执行成功"
return 0
else
log "ERROR" "$errors 个节点执行失败"
return 1
fi
}
# 使用分布式执行
nodes=("node1" "node2" "node3")
distributed_exec "${nodes[*]}" "uptime"实用脚本示例#
系统备份工具#
#!/bin/bash
# 系统备份工具
# 配置
BACKUP_DIR="/backup"
LOG_FILE="$BACKUP_DIR/backup.log"
ERROR_FILE="$BACKUP_DIR/backup.err"
SOURCES=("/etc" "/home" "/var/www")
# 日志函数
log() {
local level=$1
shift
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
local message="[$timestamp] [$level] $@"
echo "$message"
echo "$message" >> "$LOG_FILE"
if [ "$level" = "ERROR" ]; then
echo "$message" >> "$ERROR_FILE"
fi
}
# 错误处理函数
error_exit() {
log "ERROR" "$1"
exit 1
}
# 检查函数
check() {
local condition=$1
local message=$2
if [ ! $condition ]; then
error_exit "$message"
fi
}
# 清理函数
cleanup() {
log "INFO" "执行清理操作"
# 清理代码
rm -f "$BACKUP_DIR"/temp_*.tar.gz
log "INFO" "清理完成"
}
# 注册信号处理
trap cleanup EXIT
# 主函数
main() {
log "INFO" "开始系统备份"
# 检查备份目录
check [ -d "$BACKUP_DIR" ] "备份目录不存在"
check [ -w "$BACKUP_DIR" ] "备份目录不可写"
# 创建日期目录
local date=$(date +%Y-%m-%d)
local backup_dir="$BACKUP_DIR/$date"
if [ -d "$backup_dir" ]; then
log "WARNING" "备份目录已存在,使用现有目录"
else
if mkdir -p "$backup_dir"; then
log "INFO" "创建备份目录: $backup_dir"
else
error_exit "无法创建备份目录"
fi
fi
# 执行备份
for source in "${SOURCES[@]}"; do
if [ -d "$source" ]; then
local backup_file="$backup_dir/$(basename $source).tar.gz"
log "INFO" "备份 $source 到 $backup_file"
if tar -czf "$backup_file" "$source"; then
log "INFO" "备份成功"
else
log "ERROR" "备份失败"
fi
else
log "WARNING" "源目录不存在: $source"
fi
done
# 清理旧备份(保留30天)
log "INFO" "清理30天前的旧备份"
find "$BACKUP_DIR" -type d -mtime +30 -exec rm -rf {} \;
log "INFO" "系统备份完成"
return 0
}
# 执行主函数
main日志分析工具#
#!/bin/bash
# 日志分析工具
# 配置
LOG_FILE=${1:-"/var/log/syslog"}
OUTPUT_DIR="/tmp/log_analysis"
# 日志函数
log() {
local level=$1
shift
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] [$level] $@"
}
# 错误处理函数
error_exit() {
log "ERROR" "$1"
exit 1
}
# 检查函数
check() {
local condition=$1
local message=$2
if [ ! $condition ]; then
error_exit "$message"
fi
}
# 清理函数
cleanup() {
log "INFO" "执行清理操作"
# 清理代码
rm -f "$OUTPUT_DIR"/*.txt
log "INFO" "清理完成"
}
# 注册信号处理
trap cleanup EXIT
# 分析函数
analyze() {
log "INFO" "开始分析日志: $LOG_FILE"
# 检查日志文件
check [ -f "$LOG_FILE" ] "日志文件不存在"
check [ -r "$LOG_FILE" ] "日志文件不可读"
# 创建输出目录
if [ ! -d "$OUTPUT_DIR" ]; then
if mkdir -p "$OUTPUT_DIR"; then
log "INFO" "创建输出目录: $OUTPUT_DIR"
else
error_exit "无法创建输出目录"
fi
fi
# 分析错误
log "INFO" "分析错误日志"
grep "ERROR" "$LOG_FILE" > "$OUTPUT_DIR/errors.txt"
local error_count=$(wc -l < "$OUTPUT_DIR/errors.txt")
log "INFO" "找到 $error_count 条错误"
# 分析警告
log "INFO" "分析警告日志"
grep "WARNING" "$LOG_FILE" > "$OUTPUT_DIR/warnings.txt"
local warning_count=$(wc -l < "$OUTPUT_DIR/warnings.txt")
log "INFO" "找到 $warning_count 条警告"
# 分析服务状态
log "INFO" "分析服务状态"
grep "service" "$LOG_FILE" > "$OUTPUT_DIR/services.txt"
# 生成报告
log "INFO" "生成分析报告"
cat > "$OUTPUT_DIR/report.txt" << EOF
日志分析报告
=============
分析日期: $(date)
日志文件: $LOG_FILE
错误统计: $error_count 条
警告统计: $warning_count 条
详细信息:
- 错误: $OUTPUT_DIR/errors.txt
- 警告: $OUTPUT_DIR/warnings.txt
- 服务: $OUTPUT_DIR/services.txt
EOF
log "INFO" "分析完成,报告保存到: $OUTPUT_DIR/report.txt"
return 0
}
# 主函数
main() {
log "INFO" "启动日志分析工具"
analyze
log "INFO" "工具执行完成"
return 0
}
# 执行主函数
main总结#
错误处理是Shell脚本开发中的关键环节,通过掌握各种错误处理技术,您可以创建更加健壮、可靠的Shell脚本。从基础的退出状态码检查到高级的调试和错误恢复策略,本教程涵盖了Shell脚本错误处理的各个方面。
良好的错误处理不仅可以提高脚本的可靠性,还可以改善用户体验和简化维护工作。通过采用防御性编程、全面的错误检查、详细的日志记录和适当的清理操作,您可以构建能够应对各种异常情况的Shell脚本。
记住,错误处理应该是脚本设计的一部分,而不是事后添加的功能。在编写脚本时,始终考虑可能的错误情况,并为它们提供适当的处理机制。