编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

    • Shell & Bash 从0到1实战专栏
    • Shell 入门与变量
    • 流程控制与函数
    • 数据与 IO 处理
    • grep 搜索实战
    • sed 与 awk 编程
    • 文件查找与统计
    • 日志监控与告警
    • 备份进程与磁盘
    • 用户与服务管理
    • 网络调度与部署
    • 调试与脚本规范
      • 11.1 调试排错
        • 11.1.1 set -x——逐行跟踪执行
        • 11.1.2 set -e / -u / -o pipefail——故障快速暴露
        • 11.1.3 trap——信号捕获与清理
        • 11.1.4 PS4——自定义跟踪输出
        • 11.1.5 常见错误 Top 10 与排查
        • 11.1.6 shellcheck——静态分析救星
      • 11.2 脚本规范
        • 11.2.1 文件头注释模板
        • 11.2.2 函数命名与组织
        • 11.2.3 缩进/换行/行宽
        • 11.2.4 引号使用原则
        • 11.2.5 主入口与参数解析
      • 11.3 思考题
    • 安全与兼容处理
    • 性能与打包分发
  • 工具脚本

  • ScriptHub
  • Shell-Bash
杨充
2024-06-18
目录

调试与脚本规范

# 第 11 章 调试与脚本规范

# 目录介绍

  • 11.1 调试排错
    • 11.1.1 set -x——逐行跟踪执行
    • 11.1.2 set -e / -u / -o pipefail——故障快速暴露
    • 11.1.3 trap——信号捕获与清理
    • 11.1.4 PS4——自定义跟踪输出
    • 11.1.5 常见错误 Top 10 与排查
    • 11.1.6 shellcheck——静态分析救星
  • 11.2 脚本规范
    • 11.2.1 文件头注释模板
    • 11.2.2 函数命名与组织
    • 11.2.3 缩进/换行/行宽
    • 11.2.4 引号使用原则
    • 11.2.5 主入口与参数解析
  • 11.3 思考题

# 11.1 调试排错

# 11.1.1 set -x——逐行跟踪执行

set -x 让 Shell 在执行每条命令前先打印它——是排查"脚本跑到哪一行了"最直接的工具:

#!/bin/bash

# ===== 基础用法 =====
set -x                                    # 开启跟踪
name="alice"
echo "Hello $name"
set +x                                    # 关闭跟踪

# -x 输出格式(以 + 开头):
# + name=alice
# + echo 'Hello alice'

# ===== 局部调试 =====
# 不需要全局开 -x,用 {} 包裹关键区域
build_app() {
    echo "Building..."

    set -x                                 # 只跟踪这部分
    gcc -o app main.c util.c -lpthread
    set +x

    echo "Build done"
}

# ===== 执行时临时开启 =====
# bash -x script.sh                        # 整个脚本跟踪
# bash -x script.sh 2>trace.log            # 跟踪日志输出到文件(stdout 分开)

# ===== 管道中调试 =====
# 只看脚本的跟踪输出(不含正常输出)
bash -x script.sh 2>&1 | grep '^+'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 11.1.2 set -e / -u / -o pipefail——故障快速暴露

这三个选项是生产级脚本的安全带,组合使用能 90% 避免"静默失败":

#!/bin/bash

# ===== 标准开头:三大件缺一不可 =====
set -euo pipefail
# -e: 任何命令返回非 0 立即退出(快速失败)
# -u: 使用未定义变量时报错退出
# -o pipefail: 管道中任何一个命令失败,整个管道都算失败

# ===== set -e 详解 =====
# ❌ 不加 -e:错误被忽略——后续代码在错误状态运行
rm /nonexistent/file
echo "继续执行..."           # 这条也会执行!
# 输出:rm: cannot remove ... \n 继续执行...

# ✅ 加 -e
set -e
rm /nonexistent/file         # 脚本在这里退出
echo "永远不会执行"

# ===== -e 的例外情况 =====
# 以下场景不会触发 -e:
grep "pattern" file.txt || true          # || 连接的命令
if grep "pattern" file.txt; then ...      # if/while 条件中
while ! ping -c 1 host; do sleep 1; done # while 条件中

# ===== set -u 详解 =====
# ❌ 不加 -u
echo "Hello $UNDEFINED_VAR"           # 输出:Hello (无报错)

# ✅ 加 -u
set -u
echo "Hello $UNDEFINED_VAR"           # 报错:UNDEFINED_VAR: unbound variable

# 安全引用可能不存在的变量:${VAR:-default}
echo "Hello ${UNDEFINED_VAR:-world}"  # 输出:Hello world

# ===== set -o pipefail 详解 =====
# ❌ 不加 pipefail:只看管道最后一个命令的退出码
false | true
echo "退出码: $?"                      # 输出:0(true 成功了,false 的失败被忽略)

# ✅ 加 pipefail
set -o pipefail
false | true
echo "退出码: $?"                      # 输出:1(false 的失败被传递)

# ===== 实战:有选择的退出控制 =====
# 某些命令返回非 0 是正常的(如 grep 没找到)
if ! command -v jq &>/dev/null; then
    echo "jq 未安装,跳过 JSON 处理"
    # 不会触发 -e 因为用了 if 条件
fi

# 明确允许失败:|| true
set -e
cleanup_temp || true                # cleanup 失败不影响主流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 11.1.3 trap——信号捕获与清理

trap 让脚本在退出、中断、错误时自动执行清理——防止留下垃圾文件:

#!/bin/bash

# ===== trap 基础语法 =====
# trap '要执行的命令' 信号

# ===== EXIT —— 无论正常/异常退出都执行 =====
cleanup() {
    echo "清理中..."
    rm -rf "$TEMP_DIR"
    rm -f /tmp/mylock
    echo "清理完成"
}
trap cleanup EXIT

TEMP_DIR=$(mktemp -d)
# ... 脚本主体 ...
# 无论脚本正常结束还是中途报错,cleanup 都会执行

# ===== ERR —— 捕获命令失败 =====
trap 'echo "[$(date)] 错误发生在 行$LINENO 退出码$?" >> error.log' ERR

# ===== INT / TERM —— 捕获 Ctrl+C 和 kill =====
trap 'echo "收到中断信号,正在安全退出..."; exit 1' INT TERM

# ===== DEBUG —— 每条命令执行前触发(调试用)=====
trap 'echo "DEBUG: 行 $LINENO: $BASH_COMMAND"' DEBUG
# 比 set -x 更灵活,可自定义输出格式

# ===== RETURN —— 函数或 source 返回时触发 =====
my_func() {
    trap 'echo "my_func 返回"' RETURN
    echo "函数体"
}
my_func
# 输出:函数体\nmy_func 返回

# ===== trap 组合使用模板 =====
cat > /tmp/trap_template.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

TEMP_DIR=""
LOCK_FILE="/var/run/script.lock"

cleanup() {
    local exit_code=$?
    echo "[$(date)] 脚本退出,退出码: $exit_code"

    # 清理临时文件
    [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"

    # 释放锁
    [[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"

    exit $exit_code
}

trap cleanup EXIT INT TERM

# 错误时记录
trap 'echo "[$(date)] 错误: 行 $LINENO 命令 $BASH_COMMAND (退出码 $?)" >&2' ERR

main() {
    TEMP_DIR=$(mktemp -d)
    # ... 主逻辑 ...
}

main "$@"
SCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 11.1.4 PS4——自定义跟踪输出

PS4 是 set -x 输出的前缀,自定义它可以让跟踪信息包含行号、函数名和时间:

#!/bin/bash

# ===== 默认 PS4 =====
# PS4='+ '                              # 默认值
set -x
echo "test"
# 输出:+ echo 'test'

# ===== 加上行号和函数名 =====
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x

my_func() {
    local x=42
    echo "x=$x"
}
my_func
# 输出:
# +(script.sh:10): my_func(): local x=42
# +(script.sh:11): my_func(): echo 'x=42'

# ===== 加上时间戳 =====
export PS4='+[$(date "+%H:%M:%S")] ${BASH_SOURCE}:${LINENO}: '
# 输出:+[14:30:05] script.sh:10: local x=42

# ===== 实战:调试友好的 PS4 =====
export PS4='+${BASH_SOURCE}:${LINENO}(${FUNCNAME[0]:+${FUNCNAME[0]}}) '

# ===== 仅调试特定函数时生效 =====
debug_function() {
    local old_ps4="$PS4"
    export PS4='+[DEBUG] ${FUNCNAME[0]}:${LINENO}: '
    set -x
    # ... 待调试代码 ...
    set +x
    export PS4="$old_ps4"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 11.1.5 常见错误 Top 10 与排查

#!/bin/bash

# ===== 1. 变量赋值等号两边有空格 =====
# ❌
name = "alice"            # Shell 把 name 当命令,= 当参数
# ✅
name="alice"

# ===== 2. [ ] 条件测试忘加空格 =====
# ❌
if [$a -eq 1]; then       # [ 和 ] 是命令,必须和参数有空格
# ✅
if [ "$a" -eq 1 ]; then

# ===== 3. 变量未加引号——单词拆分 =====
# ❌
filename="my file.txt"
rm $filename              # 等于 rm my file.txt → 删两个文件!
# ✅
rm "$filename"

# ===== 4. 管道中的变量作用域 =====
# ❌ 管道右侧在子 shell 中运行,变量修改不会传回
count=0
cat file.txt | while read line; do
    ((count++))
done
echo "$count"             # 仍然是 0!
# ✅ 用 here-string/process substitution 避免子 shell
while read line; do
    ((count++))
done < file.txt

# ===== 5. set -e 被忽略的场景 =====
# ❌ 函数在条件中被调用——即使内部报错也不会退出 set -e
check_status() {
    false
    echo "这行还会执行"
}
if check_status; then :; fi

# ===== 6. 数组索引和 ${} 混淆 =====
# ❌ arr[0] 不需要大括号
echo ${arr[0]}             # 可以
echo $arr[0]               # ❌ 只输出 ${arr}[0] 的空值

# ===== 7. = vs == =====
# [ ] 里用 = 做字符串比较,== 是 bash 扩展
[ "$a" = "$b" ]           # POSIX
[ "$a" == "$b" ]          # bash only(但也能用)
[[ "$a" == "$b" ]]        # [[ ]] 里推荐 ==

# ===== 8. 只重定向 stderr 忘重定向 stdout =====
# ❌ command 2>&1 > file.log     # stderr 去了终端(重定向顺序错误)
# ✅ command > file.log 2>&1     # 先把 stdout 到文件,再把 stderr 跟过去

# ===== 9. crontab 时间表达式 =====
# ❌ 每天凌晨 3 点(容易写错)
# 0 3 * * *       ← 分 时 日 月 周
# * * * * 3       ← 这不是凌晨 3 点,是每周三的每分钟

# ===== 10. EOF 标识符前有空格 =====
# ❌
cat << EOF               # heredoc
EOF ← 这里不能有空格或 tab(除非用 <<-)
# ✅
cat << 'EOF'
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# 11.1.6 shellcheck——静态分析救星

#!/bin/bash

# ===== 安装 =====
# macOS:   brew install shellcheck
# Linux:   apt install shellcheck / yum install ShellCheck
# CI:      docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:stable *.sh

# ===== 使用 =====
shellcheck script.sh                       # 检查单个脚本
shellcheck --severity=error script.sh      # 只看错误级别
shellcheck -x script.sh                    # 跟踪 source 的文件一起检查
shellcheck -f json script.sh               # JSON 格式(CI 集成)

# ===== 常见 shellcheck 警告与修复 =====
# SC2086: 变量未加引号
# ❌ rm $file
# ✅ rm "$file"

# SC2164: cd 没有检查返回值
# ❌ cd /some/dir
# ✅ cd /some/dir || exit 1

# SC2068: 数组未加引号
# ❌ for f in ${files[@]}
# ✅ for f in "${files[@]}"

# SC2046: 命令替换未加引号
# ❌ for f in $(ls *.txt)
# ✅ for f in *.txt

# SC2155: declare/export 掩盖返回值
# ❌ export MYVAR=$(some_command)
# ✅ MYVAR=$(some_command); export MYVAR

# ===== 在每个脚本中消掉 shellcheck 警告 =====
# 特定行忽略:行尾加注释
# shellcheck disable=SC2034    # 忽略"变量未使用"警告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 11.2 脚本规范

# 11.2.1 文件头注释模板

#!/bin/bash
# ============================================
# 脚本名称: backup_database.sh
# 功能描述: 备份 MySQL 数据库并压缩归档
# 使用方法: ./backup_database.sh [数据库名]
#            ./backup_database.sh --all     备份所有数据库
# 参数说明:
#   -a, --all       备份所有数据库
#   -d, --db NAME   指定数据库名
#   -o, --output DIR 指定输出目录(默认 /backups)
#   -h, --help      显示帮助
# 环境变量:
#   MYSQL_PWD    MySQL 密码 (推荐用 ~/.my.cnf)
#   BACKUP_DIR   备份目录 (覆盖 -o 参数)
# 退出码:
#   0  成功
#   1  参数错误
#   2  备份失败
#   3  磁盘空间不足
# 作者:     alice
# 创建日期: 2025-06-10
# 更新日志:
#   2025-06-10 v1.0  初始版本
#   2025-07-01 v1.1  增加压缩和清理逻辑
# ============================================

set -euo pipefail
IFS=$'\n\t'                    # 防止空格/制表符导致意外分词
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 11.2.2 函数命名与组织

#!/bin/bash

# ===== 函数命名规范 =====
# 使用 snake_case(小写+下划线)
# 动词开头:get_ / set_ / check_ / do_ / parse_ / build_ / deploy_

get_config()   { ... }          # 获取值
set_defaults() { ... }          # 设置默认值
check_port()   { ... }          # 检查/验证
do_backup()    { ... }          # 执行动作
parse_args()   { ... }          # 解析输入
build_image()  { ... }          # 构建
deploy_app()   { ... }          # 部署

# ===== 函数注释 =====
# 每个函数加一行描述
# Usage: function_name <arg1> <arg2>
# 或更详细:
# @param $1 输入文件路径
# @param $2 输出目录(可选,默认 /tmp)
# @return 0 成功, 1 失败
# @stdout 处理结果
parse_config() {
    local file="$1"
    local output="${2:-/tmp}"
    # ...
}

# ===== 变量命名 =====
# 全局变量:大写
readonly CONFIG_FILE="/etc/myapp/config"
readonly MAX_RETRIES=5

# 局部变量:小写 + local 声明
function my_func() {
    local input_file="$1"
    local retries=0
    # ...
}

# ===== 函数组织顺序 =====
# 1. 全局常量
# 2. 工具/辅助函数
# 3. 核心业务函数
# 4. main() 入口
# 5. main "$@" 调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 11.2.3 缩进/换行/行宽

#!/bin/bash

# ===== 缩进:2 空格(Google Style)或 Tab(各有拥趸)=====
# 推荐 2 空格——在不同的编辑器中显示一致
if [[ -f "$file" ]]; then
  while IFS= read -r line; do
    case "$line" in
      ERROR*)
        log_error "$line"
        ;;
      WARN*)
        log_warn "$line"
        ;;
    esac
  done < "$file"
fi

# ===== 长命令换行:用 \ 续行 =====
# ✅
curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"key": "value"}' \
  "https://api.example.com/v1/data"

# 对于管道,换行符在 | 之后
find /var/log \
  -name "*.log" \
  -mtime +7 \
  -type f \
  | xargs rm -f

# ===== if 语句风格 =====
# ✅ then 和 if 同行(分号分隔)
if [[ "$x" -eq 1 ]]; then
  echo "one"
fi

# ✅ then 另起一行也可以
if [[ "$x" -eq 1 ]]
then
  echo "one"
fi

# ===== case 风格 =====
case "$1" in
  start)
    do_start
    ;;
  stop|kill)
    do_stop
    ;;
  *)
    echo "Usage: $0 {start|stop}"
    exit 1
    ;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 11.2.4 引号使用原则

#!/bin/bash

# ===== 原则:除非明确需要单词拆分,否则变量一律加双引号 =====

# ✅ 变量引用加引号
name="Alice Smith"
echo "Hello, $name"              # ✅
echo "Hello, ${name}"            # ✅ 更清晰的边界

# ❌ 不加引号的后果
echo Hello, $name                # ❌ Hello, 是参数1, Alice是参数2, Smith是参数3

# ===== 命令替换也一样 =====
# ✅
files=$(find . -name "*.txt")
echo "$files"

# ===== 需要不加引号的场景:分词是目的 =====
# 这种时候才故意不加引号:
options="-a -l -h"
ls $options                       # 故意让 Shell 把 "-a -l -h" 拆成三个参数

# ===== 单引号:所有内容原样输出 =====
echo 'Hello $name'                # 输出:Hello $name(不展开变量)

# 双引号里 $ 和 ` 仍然会展开
echo "Hello $name"                # 输出:Hello Alice Smith

# ===== heredoc 引号:决定是否展开变量 =====
cat << 'EOF'                       # 'EOF' = 不展开
Hello $name
EOF
# 输出:Hello $name

cat << EOF                         # EOF 不加引号 = 展开变量
Hello $name
EOF
# 输出:Hello Alice Smith
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 11.2.5 主入口与参数解析

#!/bin/bash
set -euo pipefail

# ---- 常量 ----
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)

# ---- 默认配置 ----
LOG_FILE="/var/log/${SCRIPT_NAME%.*}.log"
DRY_RUN=false
VERBOSE=false

# ---- 工具函数 ----
log() { echo "[$(date '+%H:%M:%S')] $*"; }
die() { log "ERROR: $*" >&2; exit 1; }

usage() {
    cat << EOF
用法: $SCRIPT_NAME [选项] <参数>

选项:
  -d, --dry-run     预览模式,不实际执行
  -v, --verbose     详细输出
  -o, --output DIR  指定输出目录
  -h, --help        显示此帮助

示例:
  $SCRIPT_NAME -v /path/to/input
  $SCRIPT_NAME --dry-run --output /tmp /path/to/input
EOF
    exit 0
}

# ---- 参数解析 ----
parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -d|--dry-run) DRY_RUN=true; shift ;;
            -v|--verbose) VERBOSE=true; shift ;;
            -o|--output)  OUTPUT_DIR="$2"; shift 2 ;;
            -h|--help)    usage ;;
            -*)           die "未知选项: $1" ;;
            *)            POS_ARGS+=("$1"); shift ;;
        esac
    done
}

# ---- 主逻辑 ----
main() {
    parse_args "$@"

    # 参数校验
    [[ ${#POS_ARGS[@]} -eq 0 ]] && die "缺少必要参数,使用 -h 查看帮助"

    log "开始执行..."

    # 核心逻辑
    # ...

    log "执行完成"
}

main "$@"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 11.3 思考题

  1. set -e 陷阱:以下代码在 set -e 下是否能正确退出?如果不能,为什么?如何修复?

    set -e
    backup() { false; echo "backup done"; }
    if backup; then echo "success"; fi
    
    1
    2
    3
  2. trap 清理链:写一个脚本,创建 3 层临时目录(A/B/C),保证脚本无论以什么方式退出(正常/Ctrl+C/kill),三层目录都被安全清理。

  3. 管道调试:有一个管道链 cmd1 | cmd2 | cmd3 | cmd4,如何快速找到是哪个环节出了问题导致的最终输出异常?

  4. shellcheck 清零:找系统中一个现有脚本,运行 shellcheck 后逐条修复所有 warning,对比修复前后的差异。

  5. 规范改造:将以下"野脚本"改造成符合本章规范的版本:

    #!/bin/sh
    a=$1
    cd /tmp/dir
    for f in $(ls); do
    rm $f
    done
    echo done
    
    1
    2
    3
    4
    5
    6
    7
#Shell#技巧
上次更新: 2026/06/17, 12:47:39
网络调度与部署
安全与兼容处理

← 网络调度与部署 安全与兼容处理→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式