401 lines
8.6 KiB
Bash
Executable File
401 lines
8.6 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# Shell 脚本调试流程示例
|
||
# 展示常见的 Bash 脚本错误和调试方法
|
||
#
|
||
|
||
# ============================================
|
||
# 调试设置
|
||
# ============================================
|
||
|
||
# 取消注释以启用调试模式
|
||
# set -x # 打印每个命令
|
||
# set -e # 错误时退出
|
||
# set -u # 未定义变量报错
|
||
# set -o pipefail # 管道中任何命令失败则失败
|
||
|
||
# 或组合使用
|
||
# set -xeuo pipefail # 严格模式
|
||
|
||
|
||
# ============================================
|
||
# 错误处理函数
|
||
# ============================================
|
||
|
||
# 错误退出函数
|
||
die() {
|
||
local message="$1"
|
||
local exit_code="${2:-1}"
|
||
echo "Error: $message" >&2
|
||
exit "$exit_code"
|
||
}
|
||
|
||
# 记录函数
|
||
log() {
|
||
local level="$1"
|
||
shift
|
||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" >&2
|
||
}
|
||
|
||
log_info() { log "INFO" "$@"; }
|
||
log_warn() { log "WARN" "$@"; }
|
||
log_error() { log "ERROR" "$@"; }
|
||
|
||
|
||
# ============================================
|
||
# 问题 1:未引用的变量
|
||
# ============================================
|
||
|
||
# 错误示例:变量未引用
|
||
demo_unquoted_variable() {
|
||
echo "=== 问题 1:未引用的变量 ==="
|
||
|
||
local name="John Doe"
|
||
|
||
# 错误:变量未引用,空值会导致语法错误
|
||
# if [ $name = "John Doe" ]; then
|
||
# echo "Match"
|
||
# fi
|
||
|
||
# 正确:始终引用变量
|
||
if [ "$name" = "John Doe" ]; then
|
||
log_info "变量匹配: $name"
|
||
fi
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 问题 2:命令失败继续执行
|
||
# ============================================
|
||
|
||
demo_command_failure() {
|
||
echo "=== 问题 2:命令失败继续执行 ==="
|
||
|
||
# 错误:cd 失败后继续执行
|
||
# cd /nonexistent_directory
|
||
# rm -rf file.txt # 会删除当前目录的文件!
|
||
|
||
# 正确:检查命令是否成功
|
||
cd /tmp || die "无法切换到 /tmp 目录"
|
||
log_info "成功切换到目录: $(pwd)"
|
||
|
||
cd - > /dev/null || true
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 问题 3:循环中的变量作用域(管道问题)
|
||
# ============================================
|
||
|
||
demo_pipeline_scope() {
|
||
echo "=== 问题 3:管道中的变量作用域 ==="
|
||
|
||
local count=0
|
||
|
||
# 错误:管道创建子 shell,外部变量不改变
|
||
# echo -e "1\n2\n3" | while read line; do
|
||
# count=$((count + 1))
|
||
# done
|
||
# echo "Count: $count" # 输出 0
|
||
|
||
# 正确:使用重定向
|
||
while read line; do
|
||
count=$((count + 1))
|
||
done < <(echo -e "1\n2\n3")
|
||
log_info "计数结果: $count"
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 问题 4:数组操作
|
||
# ============================================
|
||
|
||
demo_array_operations() {
|
||
echo "=== 问题 4:数组操作 ==="
|
||
|
||
local fruits=("apple" "banana" "cherry")
|
||
|
||
# 错误的数组访问
|
||
# echo $fruits[1] # 输出 apple[1]
|
||
|
||
# 正确的数组访问
|
||
log_info "第一个元素: ${fruits[0]}"
|
||
log_info "第二个元素: ${fruits[1]}"
|
||
log_info "所有元素: ${fruits[@]}"
|
||
log_info "数组长度: ${#fruits[@]}"
|
||
|
||
# 遍历数组
|
||
for fruit in "${fruits[@]}"; do
|
||
log_info "水果: $fruit"
|
||
done
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 问题 5:字符串比较
|
||
# ============================================
|
||
|
||
demo_string_comparison() {
|
||
echo "=== 问题 5:字符串比较 ==="
|
||
|
||
local name="John"
|
||
|
||
# 错误:数字比较使用 =
|
||
# if [ $age = 18 ]; then
|
||
|
||
# 正确:字符串比较
|
||
if [[ "$name" == "John" ]]; then
|
||
log_info "字符串匹配"
|
||
fi
|
||
|
||
# 正确:数字比较
|
||
local age=18
|
||
if [ "$age" -eq 18 ]; then
|
||
log_info "数字匹配"
|
||
fi
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 问题 6:算术运算
|
||
# ============================================
|
||
|
||
demo_arithmetic() {
|
||
echo "=== 问题 6:算术运算 ==="
|
||
|
||
local a=10
|
||
local b=5
|
||
|
||
# 错误:使用 let 或 $(())
|
||
# result = a + b # 这是命令调用
|
||
|
||
# 正确的算术运算
|
||
local result=$((a + b))
|
||
log_info "加法: $a + $b = $result"
|
||
|
||
result=$((a - b))
|
||
log_info "减法: $a - $b = $result"
|
||
|
||
result=$((a * b))
|
||
log_info "乘法: $a * $b = $result"
|
||
|
||
result=$((a / b))
|
||
log_info "除法: $a / $b = $result"
|
||
|
||
# 使用 let
|
||
let result=a+b
|
||
log_info "let 加法: $result"
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 参数验证
|
||
# ============================================
|
||
|
||
validate_arguments() {
|
||
echo "=== 参数验证 ==="
|
||
|
||
# 检查参数数量
|
||
if [ $# -lt 2 ]; then
|
||
die "用法: $0 <文件> <目录>" 2
|
||
fi
|
||
|
||
local file="$1"
|
||
local dir="$2"
|
||
|
||
# 检查文件存在
|
||
if [ ! -f "$file" ]; then
|
||
die "文件不存在: $file" 3
|
||
fi
|
||
|
||
# 检查目录存在
|
||
if [ ! -d "$dir" ]; then
|
||
die "目录不存在: $dir" 4
|
||
fi
|
||
|
||
log_info "参数验证通过"
|
||
log_info "文件: $file"
|
||
log_info "目录: $dir"
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 使用 trap 进行清理
|
||
# ============================================
|
||
|
||
demo_trap() {
|
||
echo "=== 使用 trap 进行清理 ==="
|
||
|
||
# 设置清理函数
|
||
cleanup() {
|
||
log_info "执行清理操作..."
|
||
# 这里可以执行清理操作
|
||
}
|
||
|
||
# 捕获退出信号
|
||
trap cleanup EXIT
|
||
|
||
# 捕获错误信号
|
||
trap 'log_error "发生错误,行号: $LINENO"' ERR
|
||
|
||
# 捕获中断信号
|
||
trap 'log_warn "脚本被中断"; cleanup; exit 130' INT
|
||
|
||
log_info "执行一些操作..."
|
||
# 模拟操作
|
||
sleep 1
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 调试技巧示例
|
||
# ============================================
|
||
|
||
demo_debugging() {
|
||
echo "=== 调试技巧 ==="
|
||
|
||
# 1. 使用 echo 调试
|
||
local value="test"
|
||
echo "[DEBUG] value = $value" >&2
|
||
|
||
# 2. 使用 printf 格式化输出
|
||
printf "[DEBUG] Count: %d, Name: %s\n" 42 "John" >&2
|
||
|
||
# 3. 检查变量是否设置
|
||
if [ -z "${unset_var+x}" ]; then
|
||
log_warn "变量 unset_var 未设置"
|
||
fi
|
||
|
||
# 4. 显示调用栈
|
||
log_info "调用栈:"
|
||
local i=0
|
||
while caller $i; do
|
||
((i++))
|
||
done 2>/dev/null || true
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 文件操作错误处理
|
||
# ============================================
|
||
|
||
demo_file_operations() {
|
||
echo "=== 文件操作错误处理 ==="
|
||
|
||
local tmpfile=$(mktemp) || die "无法创建临时文件"
|
||
|
||
# 确保文件被删除
|
||
trap "rm -f '$tmpfile'" EXIT
|
||
|
||
# 写入文件
|
||
echo "Test content" > "$tmpfile" || die "无法写入文件: $tmpfile"
|
||
|
||
# 读取文件
|
||
local content
|
||
content=$(cat "$tmpfile") || die "无法读取文件: $tmpfile"
|
||
|
||
log_info "文件内容: $content"
|
||
|
||
# trap 会自动清理
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 带重试的操作
|
||
# ============================================
|
||
|
||
demo_retry() {
|
||
echo "=== 带重试的操作 ==="
|
||
|
||
local max_attempts=3
|
||
local attempt=1
|
||
|
||
while [ $attempt -le $max_attempts ]; do
|
||
log_info "尝试 $attempt/$max_attempts..."
|
||
|
||
# 模拟可能失败的操作
|
||
if [ $attempt -eq 2 ]; then
|
||
log_info "成功!"
|
||
return 0
|
||
fi
|
||
|
||
log_warn "失败,重试..."
|
||
((attempt++))
|
||
sleep 1
|
||
done
|
||
|
||
log_error "所有尝试都失败了"
|
||
return 1
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 检查依赖
|
||
# ============================================
|
||
|
||
check_dependencies() {
|
||
echo "=== 检查依赖 ==="
|
||
|
||
local required_commands=("curl" "jq" "git")
|
||
|
||
for cmd in "${required_commands[@]}"; do
|
||
if ! command -v "$cmd" &> /dev/null; then
|
||
log_error "缺少依赖: $cmd"
|
||
return 1
|
||
fi
|
||
log_info "✓ $cmd 可用"
|
||
done
|
||
|
||
log_info "所有依赖都已满足"
|
||
}
|
||
|
||
|
||
# ============================================
|
||
# 主函数
|
||
# ============================================
|
||
|
||
main() {
|
||
echo "============================================"
|
||
echo "Shell 脚本调试示例"
|
||
echo "============================================"
|
||
|
||
# 运行各个示例
|
||
demo_unquoted_variable
|
||
echo ""
|
||
|
||
demo_command_failure
|
||
echo ""
|
||
|
||
demo_pipeline_scope
|
||
echo ""
|
||
|
||
demo_array_operations
|
||
echo ""
|
||
|
||
demo_string_comparison
|
||
echo ""
|
||
|
||
demo_arithmetic
|
||
echo ""
|
||
|
||
demo_trap
|
||
echo ""
|
||
|
||
demo_debugging
|
||
echo ""
|
||
|
||
demo_file_operations
|
||
echo ""
|
||
|
||
demo_retry
|
||
echo ""
|
||
|
||
check_dependencies
|
||
echo ""
|
||
|
||
log_info "所有示例执行完成"
|
||
}
|
||
|
||
# 运行主函数
|
||
main "$@"
|