引言
在 Unix-like 系统中,Bash(Bourne-Again SHell)作为最流行的命令行 shell,已成为开发者和系统管理员的必备工具。Bash 不仅仅是一个简单的命令解释器,它还提供了强大的文件路径扩展机制,即"globbing"(通配符匹配)。Globbing 允许用户使用如 *
、?
等模式来匹配文件和目录,而无需编写复杂的正则表达式或调用外部工具。这使得文件操作变得高效而直观。
然而,传统的 globbing 模式有一个显著的局限性:它无法递归遍历子目录。例如,使用 ls *.txt
只能匹配当前目录下的 .txt
文件,而忽略嵌套在子目录中的文件。这在处理复杂目录结构时常常令人沮丧,尤其是项目目录或日志文件夹中文件层层嵌套的情况下。幸运的是,Bash 4.0 版本引入了一个名为"globstar"的 shell 选项,通过 shopt -s globstar
启用,它让双星号 **
成为递归匹配的利器。 这个选项将 globbing 的能力扩展到整个目录树,让用户能够轻松实现如 ls **/*.txt
这样的递归文件查找。
本文将详细介绍 globstar 的原理、启用方法、基本与高级用法、实际应用场景,以及与传统工具如 find
的比较,确保你能立即上手。
globstar 的历史与启用方法
globstar 并非 Bash 的原创功能,它受到了其他 shell 如 Zsh 和 Ksh93 的启发。这些 shell 早在 Bash 4.0(2009 年发布)之前就已支持递归 globbing。 Bash 的引入 globstar 旨在增强其与这些现代 shell 的兼容性,同时保持向后兼容性。默认情况下,globstar 是禁用的,这避免了潜在的意外行为------因为在旧版 Bash 中,**
仅被视为单个 *
的变体,仅匹配当前目录。
要启用 globstar,只需在 Bash 会话中执行以下命令:
shopt -s globstar
这里的 shopt
是 Bash 的内置命令,用于管理 shell 选项(shell options)。-s
参数表示"set"(设置),globstar
是选项名称。执行后,你可以通过 shopt globstar
检查状态,它会输出 globstar on
。 如果想临时禁用,可以使用 shopt -u globstar
(-u
为 unset),或在脚本开头添加 shopt -u globstar
以确保兼容旧环境。
对于永久启用,你可以将 shopt -s globstar
添加到 ~/.bashrc
文件中。这样,每次启动 Bash 时都会自动加载。需要注意的是,globstar 要求 Bash 版本至少 4.0。如果你使用的是较旧的系统(如某些嵌入式设备),可能需要升级 Bash 或回退到 find
等工具。
在脚本中,检查 globstar 是否启用也很重要。可以使用条件语句:
if shopt -q globstar; then
echo "globstar 已启用"
# 执行递归 glob 代码
else
echo "globstar 未启用,请使用 find 命令"
# 备选方案
fi
shopt -q
(quiet 模式)会静默执行,并根据选项状态设置退出码(0 表示 on,非 0 表示 off)。这种检查确保脚本在不同环境中鲁棒运行。
基本用法:从简单递归查找开始
启用 globstar 后,**
的行为发生质变。它不再是简单的通配符,而是递归匹配器:**
会匹配当前目录下的所有文件和零或更多层级的目录及子目录。 最经典的示例就是用户提到的 ls **/*.txt
,它会递归查找当前目录树中所有 .txt
文件。
假设你的目录结构如下:
project/
├── README.txt
├── src/
│ ├── main.py
│ └── utils/
│ └── helper.txt
└── docs/
└── guide.txt
不启用 globstar 时,ls *.txt
只输出 README.txt
。但启用后,ls **/*.txt
会输出:
README.txt
src/utils/helper.txt
docs/guide.txt
这大大简化了文件搜索,无需 find . -name "*.txt"
的冗长语法。
另一个基本示例:列出所有子目录。使用 ls **/
会递归列出所有目录路径,如 src/ utils/ docs/
等。 注意末尾的 /
确保只匹配目录(详见高级用法)。
在 for 循环中,globstar 同样强大:
for file in **/*.log; do
echo "处理日志: $file"
# 例如:tail -f "$file"
done
这会遍历所有 .log
文件,进行批量处理,如日志分析或备份。 相比 for file in $(find . -name "*.log"); do ... done
,globstar 版本更简洁,且避免了命令替换的潜在问题(如文件名中空格)。
globstar 还支持数组赋值:
txt_files=(**/*.txt)
echo "找到 ${#txt_files[@]} 个 txt 文件"
for file in "${txt_files[@]}"; do
echo "$file"
done
${#txt_files[@]}
返回数组长度,便于统计文件数。这种方式比管道 ls **/*.txt | wc -l
更高效,因为它不创建子 shell。
高级用法:组合模式与排除技巧
globstar 的真正魅力在于与其它 glob 字符的组合,以及细粒度控制。Bash 的 glob 包括 *
(匹配任意字符序列,不包括 /
)、?
(单个字符)、[ ]
(字符类)和 { }
(大括号扩展)。启用 globstar 后,这些可以与 **
无缝结合。
首先,区分 **
和 **/
:
**
:匹配文件和目录。例如,echo **
会列出所有文件和目录路径,包括嵌套的。**/
:后跟/
时,只匹配目录和子目录。例如,printf "%s\n" **/
会输出所有目录路径,如src/ docs/ src/utils/
。 这类似于find . -type d
,但更简洁。
组合示例:查找特定子目录下的文件,如 **/{src,docs}/*.py
。这会递归匹配 src
或 docs
目录下的所有 .py
文件。 大括号 {src,docs}
是 Bash 的扩展功能,与 globstar 完美协作。
字符类示例:**/.[^.]*
匹配所有隐藏文件(以 .
开头,但不以 ..
开头)。[!.]*
排除以 .
开头的文件。 完整命令:ls **/.[!.]*
递归列出非隐藏隐藏文件?不,[!.]*
匹配不以 .
开头的文件。修正:要匹配隐藏文件,ls **/.[!.]*
实际匹配以 .
开头但第二个字符不是 .
的文件,如 .git
但排除 ..
。
排除模式是高级用法中的亮点。虽然 Bash glob 不原生支持否定,但可以通过 !
(扩展 glob)结合。启用 shopt -s extglob
后,可以使用 !(pattern)
排除。 示例:ls **/*.txt !**/backup/*.txt
查找所有 .txt
但排除 backup
目录下的。
另一个高级技巧:深度限制。虽然 globstar 默认无深度限制,但结合 {1,3}
(大括号范围)模拟:**/{1,3}/*.txt
但这不精确,因为 **
已递归。实际中,对于深度控制,仍推荐 find
。但对于模式如 **/*.{jpg,png}
,它高效匹配图像文件。
在脚本中,globstar 可用于条件匹配:
case "$1" in
**/*.zip) unzip "$1" ;;
**/*.tar.gz) tar -xzf "$1" ;;
*) echo "不支持的文件类型" ;;
esac
这根据递归路径自动解压。
与 find 命令的比较:何时选择 globstar
globstar 常常被视为 find
的轻量替代品,但两者各有优劣。find
是外部命令,支持复杂过滤(如 -mtime
时间戳、-size
大小),并可执行动作(如 -exec
)。globstar 则纯 shell 内部,速度更快,无需 fork 子进程。
比较示例:递归查找 .txt
文件。
- globstar:
wc -l **/*.txt
统计所有 txt 行数。 - find:
find . -name "*.txt" -exec wc -l {} +
类似,但更慢。
基准测试显示,在大型目录中,globstar 可快 2-5 倍,因为它避免了外部调用。 但 find
支持 -prune
排除目录,如 find . -path "./backup" -prune -o -name "*.txt" -print
,而 globstar 需要 extglob 辅助。
何时用 globstar?简单模式匹配、脚本内循环。
何时用 find?复杂查询、权限检查。
最佳实践:小项目用 globstar,大型系统用 find。
实际应用场景:从日常到脚本编写
globstar 在实际工作中无处不在。以下是几个实用场景。
1. 文件备份与同步
备份所有源代码:tar -cf backup.tar **/{*.py,*.sh}
。这打包当前目录下所有 Python 和 shell 脚本,递归子目录。 同步到远程:rsync -av **/*.html user@server:/web/
高效传输网站文件。
2. 批量文件处理
清理旧日志:rm **/*.log.(old|bak)
删除以 .old
或 .bak
结尾的日志。结合日期:但 globstar 无时间过滤,可与 find
混合:find . -name "**/*.log" -mtime +7 -delete
。
图像处理:假设有 ImageMagick,mogrify -resize 800x **/*.{jpg,png}
批量缩放所有图像。
3. 开发工作流
在 Git 项目中,搜索变更:git diff **/*.py
但 git 不直接支持;实际用 grep -r "pattern" **/*.py
递归 grep。 测试覆盖:pytest **/test_*.py
运行所有测试文件。
4. 系统管理
监控服务:ps aux | grep **/proc/[0-9]*/status
但 procfs 特殊;更实用:kill **/*.pid
清理进程 ID 文件。
在 Docker 或 CI/CD 中,globstar 简化 artifact 收集:如 GitLab CI 的 artifacts: paths: - "**/*.jar"
递归打包 JAR 文件。
5. 脚本示例:智能文件整理器
以下是一个完整脚本,利用 globstar 整理下载文件夹:
bash
#!/bin/bash
shopt -s globstar # 启用 globstar
shopt -s extglob # 启用扩展 glob
downloads="$HOME/Downloads"
cd "$downloads" || exit 1
# 移动图像
mv **/*.{jpg,jpeg,png,gif} Pictures/ 2>/dev/null
# 移动文档
mv **/*.{pdf,docx,txt} Documents/ 2>/dev/null
# 移动压缩包
mv **/*.{zip,rar,tar.gz} Archives/ 2>/dev/null
echo "整理完成!"
这个脚本会递归扫描 Downloads,分类移动文件。 你可以扩展它,添加日志或确认提示。
总结
shopt -s globstar
是 Bash 中一个低调却强大的功能,它将简单的 **
转变为递归魔力,极大简化了文件操作。从 ls **/*.txt
的日常查找,到复杂脚本的批量处理,globstar 让 Bash 更接近现代 shell 的优雅。在编写自动化脚本时,记住这一工具,它将给你节省大量时间。