跳到内容

第 0 卷 / 第 2 章 / 第 5 课

2.5 通配符、管道与重定向 ​

前置知识:环境变量和 PATH

预计时间:12 分钟

练习环境:GNU/Linux 上的 Bash 5

完成标志:能解释通配符由谁展开、管道传递哪一路数据

练习区里开始积累日志和报告,逐个输入文件名已经显得笨重。Shell 提供了两种不同的组合能力:通配符一次构造一组路径参数,管道则把一个程序的标准输出接到下一个程序的标准输入。它们经常写在同一行,却不是同一回事。

通配符由 Shell 展开 ​

bash
workdir=$(mktemp -d)
cd "$workdir"
touch report-{01,02,03}.txt photo-{a,b}.jpg notes.md

printf '%s\n' report-*.txt
printf '%s\n' photo-?.jpg
printf '%s\n' report-0[13].txt
模式含义
*任意长度的字符串
?单个字符
[13]集合中的一个字符
[a-z]范围中的一个字符,受区域设置影响

文件名展开时,*、? 通常不会跨过 /;默认也不会匹配路径分量开头的 .。因此 *.txt 不会自动包含隐藏文件或子目录里的文本文件。

{01,02,03} 是 Bash/Zsh 的花括号展开,不是 POSIX 通配符。PowerShell 的展开规则也不同。

未匹配模式的行为依 Shell 和选项而变:Bash 默认保留字面文本,Zsh 默认通常报错,Bash 的 nullglob 或 failglob 会再次改变结果。因此循环要防止把未匹配模式当文件名:

bash
for file in ./*.txt; do
  [ -e "$file" ] || continue
  printf 'would rename: %q -> %q\n' "$file" "${file%.txt}.bak"
done

确认后可把预演循环改成执行循环:

bash
for file in ./*.txt; do
  [ -e "$file" ] || continue
  mv -- "$file" "${file%.txt}.bak"
done

这里只改名字。把 .png 改成 .jpg 不会转换图片格式,必须用能解码和重新编码图片的工具。

管道连接标准输出和标准输入 ​

bash
printf '%s\n' alpha beta gamma | grep 'a' | wc -l
printf '%s\n' one two three | head -n 2

管道默认只传标准输出,不传标准错误。先用小输入验证每一段,再逐段连接,比从一条很长的命令开始更容易排错。

不要用 ps | grep name 查进程,它可能匹配 grep 自己。在 GNU/Linux 的 procps 工具集中可用:

bash
pgrep -a bash || true

macOS 的 pgrep 没有同样的 -a 语义,可改用 pgrep -fl bash。无论在哪个平台,进程名都可能与预想不同,空结果不一定表示系统异常。

不要解析 ls 来处理任意文件名,也不要把 ls | wc -l 当作可靠文件计数。隐藏文件、换行符、区域设置和展示格式都会造成误差。需要遍历时,可让 find 用 NUL 分隔文件名,再由 Bash 逐个读取:

bash
count=0
while IFS= read -r -d '' file; do
  ((count += 1))
done < <(find . -type f -print0)
printf 'files=%d\n' "$count"

本例应计数为 6。注意:循环结束不代表 find 一定成功;进程替换中的失败不会自动成为循环的退出状态,处理真实目录时还要检查权限错误。这里的进程替换 < <(...) 和 read -d 属于 Bash 语法。NUL 不可能出现在 Unix 文件名中,因此它能安全区分带空格甚至换行的路径;若要编写跨 Shell 脚本,应改用目标平台明确支持的工具或专用语言。

重定向顺序和管道状态 ​

下面的 command 是占位符,要替换成实际程序;这三行用于比较重定向规则,并非完整实验:

bash
command >out.log 2>err.log
command >all.log 2>&1
command 2>&1 >out.log

重定向从左到右处理。第二行让标准错误跟随已经指向 all.log 的标准输出;第三行先让标准错误指向原来的标准输出,随后只移动标准输出,所以结果不同。

Bash 默认使用管道中最后一个命令的退出状态。脚本希望任一环节失败都可见时可启用:

bash
set -o pipefail

但 head 提前退出可能让上游收到 SIGPIPE,在 pipefail 下要据此判断是否真是业务失败。

bash
cd
rm -r -- "$workdir"

→ 下一步:终端实战

Built with VitePress | Software Systems Atlas