🐛 第24课:脚本调试

阶段四:Shell脚本第6/6课

📚 课程目标

一、调试方法

# Bash内置调试
bash -n script.sh       # 语法检查
bash -x script.sh       # 执行追踪
bash -v script.sh       # 详细模式

# 脚本内调试
set -x    # 开启追踪
set +x    # 关闭追踪

# DEBUG陷阱
trap 'echo "LINE:$LINENO CMD:$BASH_COMMAND"' DEBUG

# 调试函数
debug() {
    [[ $DEBUG -eq 1 ]] && echo "[DEBUG] $*" >&2
}
DEBUG=1
debug "变量: var=$var"

二、shellcheck

# 安装
sudo apt install shellcheck

# 使用
shellcheck myscript.sh
shellcheck -S error myscript.sh   # 仅错误

# 常见问题
# SC2086: 引用变量
echo $var          # 错误
echo "$var"        # 正确

# SC2155: 分开声明和赋值
local var=$(cmd)   # 不推荐
local var; var=$(cmd)  # 推荐

# 忽略检查
# shellcheck disable=SC2086
echo $SPECIAL_VAR

三、常见错误

# 等号空格
name = "hello"    # 错误!
name="hello"      # 正确

# if缺少空格
if[$x -eq 1]      # 错误!
if [[ $x -eq 1 ]] # 正确

# 未引用变量
file="my doc.txt"
rm $file           # 删两个文件!
rm "$file"         # 正确

# 管道子Shell变量不传回
count=0
cat file | while read line; do
    ((count++))    # 子Shell中修改
done
echo $count        # 0!
# 修复: while read ... done < file

四、错误处理

#!/bin/bash
set -euo pipefail

# trap捕获
cleanup() {
    echo "清理临时文件..."
    rm -f /tmp/myapp_$$.*
}
trap cleanup EXIT INT TERM

# 错误处理函数
error_handler() {
    echo "错误: 第$1行" >&2
}
trap 'error_handler $LINENO' ERR

# 安全执行
safe_exec() {
    local desc="$1"; shift
    if eval "$@"; then
        echo "✅ $desc"
    else
        echo "❌ $desc" >&2
        return 1
    fi
}

# 带重试
retry() {
    local max="$1"; shift
    local n=1
    while [[ $n -le $max ]]; do
        eval "$@" && return 0
        echo "重试 $n/$max..."
        ((n++)); sleep 2
    done
    echo "重试失败" >&2; return 1
}

五、日志记录

LOG="/tmp/$(basename "$0" .sh).log"
VERBOSE=0

log() {
    local level="$1"; shift
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
    echo "$msg" >> "$LOG_FILE"
    [[ "$VERBOSE" -eq 1 || "$level" != "DEBUG" ]] && echo "$msg"
}
info()  { log "INFO" "$@"; }
warn()  { log "WARN" "$@"; }
error() { log "ERROR" "$@" >&2; }

info "脚本开始"
warn "磁盘不足"
error "连接失败"

六、健壮脚本模板

#!/bin/bash
set -euo pipefail

readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly LOG_FILE="/tmp/${SCRIPT_NAME}.log"
readonly TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT

log() { echo "[$(date '+%T')] $*" | tee -a "$LOG_FILE"; }
die() { log "FATAL: $*" >&2; exit 1; }

require_cmd() {
    command -v "$1" &>/dev/null || die "需要命令: $1"
}

require_root() {
    [[ $EUID -eq 0 ]] || die "需要root权限"
}

# 参数解析
while getopts "hv" opt; do
    case $opt in
        h) echo "Usage: $0 [options]"; exit 0 ;;
        v) set -x ;;
        *) exit 1 ;;
    esac
done

main() {
    require_cmd curl
    log "开始执行"
    # 业务逻辑
    log "完成"
}
main "$@"

七、练习

📝 找出并修复脚本问题

# 问题脚本
#!/bin/bash
name = $1
if [ $name = "" ]; then
    echo "需要参数"
    exit
fi
for f in $(ls *.txt); do
    cat $f | grep error
    echo "errors in $f"
done

# 修复后:
#!/bin/bash
set -euo pipefail
name="$1"
if [[ -z "$name" ]]; then
    echo "需要参数" >&2; exit 1
fi
for f in *.txt; do
    if grep -q error "$f"; then
        echo "errors in $f"
    fi
done
🏆

成就解锁:脚本调试专家

✅ 掌握调试方法

✅ 学会shellcheck

✅ 理解常见错误

✅ 掌握错误处理

✅ 学会健壮脚本编写

🎉 阶段四(Shell脚本)完成!下一阶段:自动化

八、脚本测试框架

# 使用bats测试Shell脚本
# 安装
sudo apt install bats

# test_myapp.bats
#!/usr/bin/env bats

@test "帮助信息" {
    run ./myapp.sh -h
    [ "$status" -eq 0 ]
    [[ "$output" =~ "Usage" ]]
}

@test "参数校验" {
    run ./myapp.sh
    [ "$status" -eq 1 ]
}

@test "正常执行" {
    run ./myapp.sh test
    [ "$status" -eq 0 ]
}

# 运行测试
bats test_myapp.bats

九、CI/CD中的Shell脚本

# .gitlab-ci.yml 示例
stages:
  - test
  - deploy

test:
  stage: test
  script:
    - shellcheck *.sh
    - bats tests/

deploy:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main

# deploy.sh
#!/bin/bash
set -euo pipefail
ansible-playbook -i inventory deploy.yml

十、脚本文档化

#!/bin/bash
# 脚本: deploy.sh
# 描述: 部署应用到生产环境
# 用法: ./deploy.sh [-h] [-e ENV] APP_NAME
# 选项:
#   -h        显示帮助
#   -e ENV    部署环境(staging/production)
# 作者: ops-team
# 创建: 2024-01-15
# 更新: 2024-03-20 - 添加回滚功能

# 使用shdoc生成文档
# shdoc < deploy.sh > deploy.md

附录:脚本调试学习资源

推荐书籍

书名作者重点
Linux命令行与Shell脚本编程大全BlumShell脚本全面覆盖
UNIX/Linux系统管理技术手册Nemeth系统管理圣经
Ansible: Up and RunningLintnerAnsible实战
鸟哥的Linux私房菜鸟哥中文经典入门

在线资源

认证路径

认证机构级别
LFCSLinux Foundation基础系统管理
LFCELinux Foundation高级系统工程师
RHCSARed HatRed Hat管理员
RHCERed HatRed Hat工程师
CKA/CKADCNCFKubernetes管理员/开发者

实践环境

常用速查表

# 文本处理速查
grep -E 'pattern' file     # 扩展正则搜索
sed -i 's/old/new/g' file  # 替换
awk '{print $1}' file     # 提取列
sort | uniq -c | sort -rn   # 频次统计
cut -d: -f1 file            # 切列
tr 'A-Z' 'a-z'             # 转小写

# 系统管理速查
systemctl status/start/stop/restart service
journalctl -u service -f    # 跟踪日志
df -h / du -sh *            # 磁盘使用
ps aux --sort=-%mem         # 内存排序
ss -tlnp                    # 监听端口
iptables -L -n              # 防火墙规则
crontab -e                  # 编辑定时任务
useradd -m -s /bin/bash user # 创建用户

# 网络诊断速查
ip addr / ip route          # 网络配置
ping / traceroute           # 连通测试
dig / nslookup              # DNS查询
curl -I url                 # HTTP头检查
nc -zv host port            # 端口测试
tcpdump -i eth0 port 80     # 抓包

十、实机验证记录

$ bash -n -c 'echo hello'
(无输出 = 语法正确)
$ which shellcheck 2>/dev/null || echo "shellcheck: 未安装"
shellcheck未安装

十一、生产脚本监控

#!/bin/bash
# script-monitor.sh
SCRIPTS=("/opt/backup.sh" "/opt/sync.sh")
for s in "${SCRIPTS[@]}"; do
    [[ ! -f "$s" ]] && echo "Missing: $s" && continue
    pid=$(pgrep -f "$s" | head -1)
    [[ -n "$pid" ]] && echo "$s running PID=$pid" || echo "$s idle"
done

# 脚本超时检测
timeout_check() {
    local pid="$1" max_sec="$2"
    local sec=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
    [[ -n "$sec" ]] && [[ $sec -gt $max_sec ]] && echo "TIMEOUT: $pid ${sec}s"
}

专题:脚本质量保证体系

在实际生产环境中,脚本质量保证体系是系统管理员必须深入掌握的核心能力。本节将探讨常见场景、最佳实践和避坑指南。

常见问题与解决方案

问题1:脚本在生产环境运行异常

开发环境和生产环境的差异是脚本出错的常见原因。建议:

问题2:性能不达预期

优化策略:

问题3:可维护性差

改善方法:

最佳实践总结

#!/bin/bash
# 脚本质量保证体系最佳实践模板
set -euo pipefail

# 1. 参数校验
[[ $# -lt 1 ]] && echo "Usage: $0 <arg>" >&2 && exit 1

# 2. 环境检查
require_cmd() {
    command -v "$1" >/dev/null || { echo "需要: $1" >&2; exit 1; }
}

# 3. 清理机制
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

# 4. 日志记录
log() { echo "[$(date +%T)] $*"; }
err() { log "ERROR: $*" >&2; exit 1; }

# 5. 主逻辑
main() {
    log "开始处理: $1"
    # 业务逻辑
    log "处理完成"
}

main "$@"

进阶方向

延伸阅读:实用命令速查

# 文件操作
ls -la                    # 详细列表
find / -name "*.conf" 2>/dev/null  # 查找文件
ln -s target link         # 符号链接
du -sh * | sort -rh       # 目录大小排序

# 文本处理
grep -rn "pattern" dir/   # 递归搜索
sed -i.bak 's/old/new/g' f  # 备份替换
awk -F: '{print $1}' file  # 提取列
sort file | uniq -c        # 去重计数

# 系统管理
systemctl status/start/stop/restart service
journalctl -u service -f  # 实时日志
ps aux --sort=-%mem        # 内存排序
ss -tlnp                   # 监听端口
crontab -e                 # 定时任务

# 网络诊断
ip addr/route/show         # 网络配置
ping/traceroute            # 连通测试
dig/nslookup               # DNS查询
curl -I url                # HTTP检查
nc -zv host port           # 端口测试

# 磁盘管理
df -h                      # 磁盘使用
fdisk -l                   # 分区信息
mkfs.ext4 /dev/sdb1        # 格式化
mount /dev/sdb1 /mnt       # 挂载
blkid                      # UUID查看

总结

本章内容是Linux系统管理课程的重要组成部分。掌握这些知识需要大量的实践操作,建议在虚拟机或云服务器上反复练习。记住:纸上得来终觉浅,绝知此事要躬行。

在实际工作中,遇到问题时:

  1. 先看日志(journalctl/dmesg/var/log)
  2. 再查文档(man/info/--help)
  3. 然后搜索(Google/Stack Overflow)
  4. 最后实验(在测试环境验证)