翻译 | Bash 扩展通配符 (ExtGlob)

英文原文: Bash Extended Globbing
作者: Mitch Frazier
摘要: Bash 扩展通配符的科普文章。

在 Bash 中,通配符称为 路径名扩展 (pathname expansion)。路径名扩展有时也被称为 globbing
当您将它们作为命令的一部分键入时,路径名扩展将“扩展”*, ?[...] 语法,例如:

1
2
3
$ ls *.jpg         # 列出所有 .jpg 文件
$ ls ?.jpg # 列出文件名只有一个字的 .jpg 文件 (如 a.jpg, 1.jpg, 哈.jpg)
$ rm [A-Z]*.jpg # 删除以大写字母开头的 .jpg 文件

关于路径名扩展,一个鲜为人知的细节,它是由 Bash 解析执行,而不是由操作系统或正在运行的程序。
在运行该程序之前,Bash 会将扩展符替换为命令行,所以该程序不会看到通配符。
如果你直接通过 exec() 或在其他代码中调用程序,而不是通过 Bash 执行,那么你调用的命令中的通配符是不会被解析的。

以上这些只是 Bash 支持的基础通配符。
我们还可以通过以下命令开启扩展通配符:

1
$ shopt -s extglob

Bash 手册的扩展通配符文档如下:

1
2
3
4
5
?(pattern-list)   匹配零次或一次给定的表达式列表
*(pattern-list) 匹配零次或多次给定的表达式列表
+(pattern-list) 匹配一次或多次给定的表达式列表
@(pattern-list) 匹配给定的表达式列表
!(pattern-list) 匹配除给定表达式列表之外的内容

这里的表达式列表(pattern-list),是由 | (又名管道符号)分隔的表达式列表。
如果你熟悉正则表达式,那么可以用如下的语法对应:

1
2
3
4
5
6
Bash              正则表达式
?(pattern-list) (...|...)?
*(pattern-list) (...|...)*
+(pattern-list) (...|...)+
@(pattern-list) (...|...) [@ 不是正则语法,忽略即可]
!(pattern-list) (?!...|...).*

除了 @ 以外,其他都可以跟正则对应上。
例如,要列出以 “ab” 或 “def” 开头的所有 jpg 和 gif 文件,您可以这样做:

1
$ ls @(ab|def)*.@(jpg|gif)

如果没有开启扩展通配符,那么就需要如下命令:

1
ls ab*.jpg ab*.gif def*.jpg def*.gif

列出正则表达式为 ab(2|3)+.jpg 的文件,命令如下:

1
$ ls ab+(2|3).jpg

这是你无法通过基础通配符做到的,这个扩展通配符会匹配:ab2.jpg, ab3.jpg, ab2222.jpg, ab333.jpg 等文件。

如果你想用 “!(…)” 匹配除了 .jpg, .gif 以外的文件时,人们的第一个想法可能是这样的:

1
$ ls *!(.jpg|.gif)

因为 * 会优先匹配到整个文件名,而导致 “!(…)” 不被执行。
正确的做法如下:

1
$ ls !(*.jpg|*.gif)

我们再来尝试一个更加复杂的例子吧。
列出所有不是 .jpg, .gif 并以 “ab” 或 “def” 开头文件:
可能有点绕,正则是 (?!(ab|def).*\.(jpg|gif)).*

1
$ ls !(@(ab|def)*.@(jpg|gif))

当然,就像复杂的正则表达式一样,在写完10分钟后,这将完全无法理解。