Bash中的模式匹配”glob patterns“和”extglob“

Sunday, December 15, 2019

“glob patterns”(也有wildcard patterns或globbing的说法,指的大体是一个东西,我不太了解个中差异)用以匹配文件名或路径名,类似正则表达式,但正则表达式主要用来匹配文本, 相比glob patterns更加强大,但更慢。UNIX中曾有 \etc\glob 后来被整合进shell中并一直延续到现在。在Bash中glob patterns也可以在test语句和case语句中使用。

glob paterns的语法十分简单

PatternDescription
*匹配任意数量的任意字符
?匹配一个任意字符
[...]字符集,匹配一个中括号内列出的字符,支持 [a-z] 这种范围表示法,支持POSIX标准定义的字符集(见文末
[!...]同上,但匹配一个中括号内没有列出的字符

来几个实例感受一下

# 先建立实验环境
$ cd /tmp
$ mkdir test && cd test
$ touch a b c ab ac bc abc bcd cde 

$ ls ?
a  b  c

$ ls ??
ab  ac  bc

$ ls *
a  ab  abc  abcd  ac  b  bc  bcd  c  cde

$ ls a*
a  ab  abc  abcd  ac

$ ls [ab]*
a  ab  abc  abcd  ac  b  bc  bcd

$ ls [a-c][bc]
ab  ac  bc

有几个需要注意的地方:

  • [^...] 在Bash中的行为与 [!...] 相同,但不建议使用,在POSIX标准中 [^...] 的行为是未定义的

  • 匹配隐藏文件(文件名以 . 开头)应当明确指出

$ touch .a .b .c  

$ echo *
a ab abc abcd ac b bc bcd c cde

$ echo .*
. .. .a .b .c
  • 不要多此一举使用引号把模式包起来, shell会认为那仅仅是个字符串而采用另外的处理方式
$ echo a*
a ab abc abcd ac

$ echo "a*"
a*

$ ls "a[a-c]"
ls: cannot access 'a[a-c]': No such file or directory
  • * ? [...] 都无法匹配 / ,因此需要匹配子文件夹内容时需明确指出
# 目录结构
$ tree --charset X
.
|-- abc
|-- child1
|   `-- abc
|-- child2
|   `-- abc
`-- child3
    `-- bbc
    
3 directories, 4 files

$ echo child*
child1 child2 child3

$ echo child?/*
child1/abc child2/abc child3/bbc
  • 一个合格的文件名不应包含奇怪的字符(最好只包含个字符集 [0-9a-zA-Z_-.] ),因此我不打算解释像下列这样你这辈子都不会用上的奇怪写法具体是怎么运行的,有兴趣的自行 man glob.7
$ ll []a[][a[]
-rw-r--r-- 1 glaumar glaumar 0 Dec 13 22:44 '[a'
-rw-r--r-- 1 glaumar glaumar 0 Dec 13 22:47  ]a
-rw-r--r-- 1 glaumar glaumar 0 Dec 13 22:35 'a['
-rw-r--r-- 1 glaumar glaumar 0 Dec 13 23:11  aa

Bash对glob的扩展

如果你有正则表达式基础会发现glob patterns中没用量词,尽管很少需要但确实有些让人不爽,所幸GNU在Bash中进行了相应扩展

PatternDescription
?(pattern-list)括号内模式匹配0次或1次
*(pattern-list)括号内模式匹配0次到多次
+(pattern-list)括号内模式匹配1次到多次
@(pattern-list)括号内模式匹配1次
!(pattern-list)匹配括号内的模式以外的其他内容

默认情况下Bash没有开启这些扩展,需手动启动

# 查看是否开启 extglob
$ shopt extglob
extglob        	off

# 开启 extglob
$ shopt -s extglob

extglob 语法都十分简单明了,这里不作过多演示,唯一你可能令你感到疑惑的或许是 @(pattern-list) ,只匹配一次看起来毫无作用,显得有些多余。多说无益,看例子

$ touch ab1234 cd1234

$ ls @(ab|cd)1234
ab1234  cd1234

pattern-list 是一个或多个使用 | 隔开的模式, @(pattern1|pattern2) 与正则表达式中的 (pattern1|pattern2) 效果相同,类似的 ?(pattern1|pattern2) *(pattern1|pattern2) +(pattern1|pattern2) 对应 (pattern1|pattern2)? (pattern1|pattern2)* (pattern1|pattern2)+

或许你觉得 extglob 的语法显得臃肿和丑陋,例如正则表达式中简单的 a?bc|xyz 写成glob pattern却是 @(?(a)bc|xyz) 。这些丑陋的语法体现的更多是一种妥协,要向下兼容,要方便交互式地使用,不能与shell现有语法冲突……

POSIX标准定义的字符集

POSIXDescriptionASCII
[:alnum:]数字和字母[a-zA-Z0-9]
[:alpha:]字母[a-zA-Z]
[:ascii:]ASCII字符[\x00-\x7F]
[:blank:]空格和 Tab[ \t]
[:cntrl:]控制字符[\x00-\x1F\x7F]
[:digit:]数字[0-9]
[:graph:]可视字符[\x21-\x7E]
[:lower:]小写字母[a-z]
[:print:]可打印字符[\x20-\x7E]
[:punct:]标点符号[!"#$%&'()*+, -./:;<=>?@[ ]^_‘{|}~]
[:space:]所有空白字符[ \t\r\n\v\f]
[:upper:]大写字母[A-Z]
[:word:]单词[A-Za-z0-9_]
[:xdigit:]十六进制数[A-Fa-f0-9]

注意:[] 也是该字符集名称的一部分,即在使用中和 [0-9] 等价的是 [[:digit:]] 而不是 [:digit:]


参考:

shell

Shell特殊变量

基本正则表达式(BRE)和扩展正则表达式(ERE)