第 4 章 · 03 练习与调试最佳实践


文档摘要

第 4 章 · 03 练习与调试最佳实践 语法学完只算"认得字",动手写题才算"会作文"。本节把经典题库中的 12 道脚本练习按难度分成五组逐题精讲,随后转入工程化三件套:set -e 失败即停、bash -x 逐行追踪、bats 自动化测试,最后汇总一份"新手的坑"清单。 学习目标 独立完成 12 道练习,覆盖变量、算术、条件、循环、函数、case、文件与进程 理解 set -e 的语义边界,用 || true 等手法精确控制失败策略 用 bash -x 定位脚本执行路径 认识 bats 测试框架的用例骨架 避开等号空格、漏引号、忘权限、CRLF 换行等高频坑 一、练习全景:12 题五组 组别 | 练习 | 主题 | 难度 | 考点 入门三连 | Hello World | 变量 |

第 4 章 · 03 练习与调试最佳实践

语法学完只算"认得字",动手写题才算"会作文"。本节把经典题库中的 12 道脚本练习按难度分成五组逐题精讲,随后转入工程化三件套:set -e 失败即停、bash -x 逐行追踪、bats 自动化测试,最后汇总一份"新手的坑"清单。

学习目标

  • 独立完成 12 道练习,覆盖变量、算术、条件、循环、函数、case、文件与进程
  • 理解 set -e 的语义边界,用 || true 等手法精确控制失败策略
  • 用 bash -x 定位脚本执行路径
  • 认识 bats 测试框架的用例骨架
  • 避开等号空格、漏引号、忘权限、CRLF 换行等高频坑

一、练习全景:12 题五组

组别 练习 主题 难度 考点
入门三连 Hello World 变量 基础 定义与引用
Basic Date 变量 基础 命令替换
Great Day 变量 基础 参数缺省值
算术与条件 Factors 算术 基础 取模与 if
Argument Check 条件 基础 参数校验
循环与输入 Files Size For 循环 基础 遍历与统计
Count Chars 输入+While 基础 read 逐行
函数与分支 Sum 函数 基础 参数累加
Num of Args Case 基础 多路分派
杂项进阶 Empty Files 杂项 基础 空文件检测
Directories Comparison 杂项 基础 目录对比
Host Status 杂项 进阶 主机探活

从"入门三连"到"主机探活",难度与考察范围逐组攀升。下面按组精讲,每题给出思路与参考答案。

二、逐题精讲

入门三连:变量与参数

第一题 Hello World 只考一件事——变量定义与引用:

#!/bin/bash HW="Hello World" echo $HW

写对它的前提是记住"等号两侧无空格"。第二题 Basic Date 引入命令替换:

#!/bin/bash DATE=$(date) echo $DATE

date 命令的输出装进变量再打印,是 $() 的最小完整用例。第三题 Great Day 升级为"有参数打印参数、无参数打印默认值":

#!/bin/bash echo "${1:-yay}"

一行代码同时考察了位置参数 $1、缺省值展开 :- 与双引号安全引用三个知识点,是面试常考的最小健壮脚本。

算术与条件:判断参数

Factors 题要求判断"给定数字是否有 4 这个因数",取模运算登场:

#!/bin/bash if [ $(($1 % 4)) -eq 0 ]; then echo "4 的倍数" else echo "不是 4 的倍数" fi

$(( )) 算术替换取余,-eq 数值比较,三处语法环环相扣。Argument Check 题转向"参数是否存在"的校验:

#!/bin/bash if [ $# -eq 0 ]; then echo "请传入参数" exit 1 fi echo "收到 $# 个参数"

$# 统计参数个数,exit 1 用非零退出码向调用方报错——脚本的"契约"意识从这里开始。

循环与输入:批量与逐行

Files Size 题遍历一批文件并报告大小:

#!/bin/bash for f in "$@"; do stat -c '%s' "$f" # 或 ls -l "$f" | awk '{print $5}' done

循环体可以换成任意处理逻辑,比如累加出"总大小"。Count Chars 题反向操作——从标准输入逐行读、逐行数字符数:

#!/bin/bash while read line; do echo "${#line}" done

${#var} 求长度配合 while+read 逐行处理,正是文本管道的标准姿势。

函数与分支:复用与分派

Sum 题把"参数求和"封装成函数:

#!/bin/bash sum() { total=0 for n in "$@"; do total=$((total + n)) done echo $total } sum "$@"

函数负责纯计算、用 echo 输出结果,主流程调用并接住输出。Num of Args 题按参数个数分派输出,是 case 的考题:

#!/bin/bash case $# in 0) echo "没有参数" ;; 1) echo "只有一个参数" ;; *) echo "有多个参数" ;; esac

杂项进阶:文件与进程

Empty Files 题找出空文件,组合 find 与 test:

#!/bin/bash find . -type f -empty # 或循环版: for f in "$@"; do [ -s "$f" ] || echo "$f 是空文件" done

-s 判断非空,|| 短路实现"为空则报告"。Directories Comparison 题对比两个目录的差异:

#!/bin/bash diff <(ls -1 dir1) <(ls -1 dir2)

这题正是上一节讲的进程替换的用武之地——diff 需要两个"文件",而数据源是两条命令输出。最后一道进阶题 Host Status 探活主机:

#!/bin/bash if ping -c 2 -W 1 "$1" > /dev/null 2>&1; then echo "$1 在线" else echo "$1 不可达" fi

> /dev/null 2>&1 吞掉噪音输出、只留退出码判断,是探活脚本的标准写法;进阶版可换成 curl 探 HTTP 服务并检查 HTTP 状态码。

三、set -e:失败即停

Shell 脚本的默认行为反直觉:某条命令失败,脚本不会停下,会继续往下跑。这意味着一个 rm 失败后,后续假设文件已删除的逻辑仍会执行,错误被层层放大。

set -e(errexit)改变这个行为:任何命令返回非零退出码,脚本立即退出。它是脚本的第一道防线,通常与 set -u(变量未定义即报错)搭配:

set -euo pipefail

三件套含义:-e 失败即停、-u 未定义变量即错、pipefail 让管道中任一环节失败都算失败(默认只看最后一段)。这是现代 Bash 脚本的"标准安全头"。

但 set -e 有边界,需要精确控制的地方要显式豁免:

  • 明知某命令可能失败、且失败可接受时,追加 || true 兜底;
  • 条件判断里的命令(if/while 的条件表达式)不受 -e 影响,这是刻意设计——判断"失败与否"本身就是目的;
  • 管道中只有最后一段受 -e 管束,所以需要 pipefail 补位。

四、bash -x:逐行追踪

定位脚本问题,第一利器是 bash -x script.sh(或脚本内 set -x):每条命令执行前打印展开后的完整形式(带 + 前缀),变量值一目了然:

$ bash -x hello.sh + HW='Hello World' + echo 'Hello World' Hello World

从输出能直接看出:变量是否展开、引号是否生效、参数是否切分。老派但同样有效的是在关键路径打 echo 断点("old good way"),确认"执行到了这里"和"此时的值是什么"。

调试纪律:先 -x 看执行路径,再在可疑点加 echo,修复后务必移除调试输出。

五、坑清单:新手必踩的六个雷

错误写法 正确写法
赋值带空格 x = 2 x=2
引用不加引号 echo $1 | echo "$1"
忘了执行权限 ./hello.sh 报拒绝 chmod +x hello.sh
Windows 换行 $'\r': command not found | 用 LF 换行(脚本内 sed -i 's/\r$//'
混用 * 与 @ "$*" 传参被拼碎 | "$@" 保持参数完整
正则放引号内 [[ $x =~ "^[0-9]+$" ]] 失配 模式存变量或用无引号字面量

其中 CRLF 换行是 Windows 用户写 Linux 脚本的头号杀手:编辑器保存了 \r\n,Bash 把 \r 当成参数内容,报错信息莫名其妙,务必以 LF 保存。

六、bats:脚本也能自动化测试

脚本是代码,代码就该有测试。Bats(Bash Automated Testing System)是 Bash 生态的事实标准测试框架,用例骨架极简:

#!/usr/bin/env bats @test "无参数时输出 yay" { result="$(echo "${1:-yay}")" [ "$result" = "yay" ] } @test "有参数时输出参数" { result="$(echo "hello")" [ "$result" = "hello" ] }

每个 @test 块是一个用例,块内用 [ ] 断言,全部通过则测试绿。把练习题的输出抓进变量、与期望值比对,12 道练习就变成了 12 组回归用例——以后改脚本不怕改坏旧功能。

小结

本章从词法(变量与参数)到语法(条件、循环、函数)再到工程化(练习、set -e、bash -x、bats),完成了 Shell 脚本的闭环。12 道练习覆盖了脚本编程的绝大多数基础模式,set -euo pipefail 与 bats 则让脚本从"能跑"走向"敢交付"。Shell 是 DevOps 的底层母语,但它处理的是"单机上的命令流"——当应用要跨机器、跨环境交付时,容器化是下一站。

下一节预告

第 5 章《容器化与 Docker》将把"可执行环境"打包成镜像:镜像分层、Dockerfile 指令、容器与虚拟机的关系,以及镜像构建与分发的最佳实践。


发布者: 作者: 灏天文库 转发
评论区 (0)
U