2.7 终端综合练习
前置知识:本章前五页
预计时间:20~25 分钟
练习环境:GNU/Linux 上的 Bash 5
完成标志:能独立完成实验,并解释每次展开和重定向
现在从空目录完成一次连续任务。实验不修改家目录里的启动文件,也不依赖 /var/log;所有产物都留在 mktemp 创建的目录中。
任务一:建立项目骨架
bash
workdir=$(mktemp -d)
printf 'workdir=%s\n' "$workdir"
cd "$workdir"
mkdir -p project/{src,docs,logs,bin}
printf '%s\n' '# Workshop Project' > project/README.md
printf '%s\n' 'first note' > project/docs/notes.txt
printf '%s\n' 'second note' >> project/docs/notes.txt
touch project/logs/app-{01,02,03}.log
find project -print检查点:目录结构完整;notes.txt 有两行;三个日志文件存在。
任务二:创建只在练习区生效的命令
bash
cat > project/bin/workshop-status <<'SCRIPT'
#!/bin/sh
printf 'root=%s\n' "$WORKSHOP_ROOT"
find "$WORKSHOP_ROOT" -type f -print
SCRIPT
chmod +x project/bin/workshop-status
export WORKSHOP_ROOT="$workdir/project"
original_path=$PATH
PATH="$workdir/project/bin:$PATH"
export PATH
type -a workshop-status
workshop-status检查点:type -a 指向临时目录;子进程能读到 WORKSHOP_ROOT。
任务三:先预演批量改名
bash
cd "$WORKSHOP_ROOT/logs"
for file in ./*.log; do
[ -e "$file" ] || continue
printf 'would rename: %q -> %q\n' "$file" "${file%.log}.bak"
done确认三个目标都正确后执行:
bash
for file in ./*.log; do
[ -e "$file" ] || continue
mv -- "$file" "${file%.log}.bak"
done
printf '%s\n' ./*.bak检查点:只改了扩展名,文件内容没有转换。若处理图片,改名不等于格式转换。
任务四:观察标准输出和标准错误
bash
cd "$workdir"
sh -c 'printf "normal\n"; printf "problem\n" >&2; exit 7' \
>out.log 2>err.log || status=$?
printf 'status=%s\n' "${status:-0}"
printf 'stdout: '; cat out.log
printf 'stderr: '; cat err.log检查点:退出状态是 7,两个文件各有一行。然后把重定向改成 >all.log 2>&1,观察两行如何进入同一个文件。
任务五:可靠地遍历和计数
bash
count=0
while IFS= read -r -d '' file; do
((count += 1))
done < <(find project -type f -print0)
printf 'files=%d\n' "$count"按本页步骤完成时,files=6:README、笔记、自定义脚本和三个日志文件;out.log、err.log 在 project 外,不计入。这个循环没有传播进程替换中 find 的退出状态,真实目录的读取失败还要另行检查。它比解析 ls 更能承受空格和换行等特殊文件名,因为 NUL 不可能出现在 Unix 文件名中。代码使用了 Bash 的进程替换和 read -d;如果脚本必须在 /bin/sh、macOS 和 Linux 之间通用,应重新选择这些平台共同支持的实现,不要只把首行解释器改掉。
排障顺序
pwd:是否在预期目录?printf '<%s>\n' "$variable":变量是否为空或含意外空格?type -a command:执行的究竟是哪一个命令?ls -ld -- "$path":目标是否存在、权限是否足够?printf '%q\n' "$path":Bash 中路径展开后到底是什么?status=$?:失败的是哪一步?不要在读取状态前先运行另一条命令。
清理与验收
bash
cd
ls -ld -- "$workdir"
rm -r -- "$workdir"
PATH=$original_path
export PATH
unset original_path WORKSHOP_ROOT
hash -r 2>/dev/null || true验收时,试着不回看答案,从空目录重做五个任务。做完后,你应当能解释变量为什么加引号、路径前为什么使用 --,以及练习为什么不直接修改 Shell 启动文件。
你现在能确认当前位置、检查命令来源,并在执行前看清批量操作的范围。下一章使用 Git 记录文件变化,让已经提交的状态可以比较和恢复。