Linux Bash 通配符详解
通配符允许您简洁地指定用于匹配一组文件名的模式(例如,*.pdf
模式可以获得所有 PDF 文件列表)。通配符通常也被称为 glob 模式。但是,glob 模式的用途不仅仅只能生成一个有用的文件名列表。Bash man 帮助页面中将 glob 模式简单地称为 “Pattern Matching(模式匹配)”。
通配符模式语法
首先,让我们快速回顾一下 Bash 的 glob 模式的语法。除了众所周知的简单通配符之外,Bash 还有扩展的 globbing,其增强了额外的特性。这些扩展的特性是通过 extglob 选项启用的。
模式 |
|
---|---|
* | 匹配任何字符串,包括空字符串 |
? | 匹配任何单个字符 |
[…] | 匹配在指定集合中任何单个字符 |
?(pattern-list) | 匹配零个或一个指定模式的匹配项(extglob) |
*(pattern-list) | 匹配零个或更多指定模式的匹配项(extglob) |
+(pattern-list) | 匹配一个或更多指定模式的匹配项(extglob) |
@(pattern-list) | 匹配其中一个指定模式的匹配项(extglob) |
!(pattern-list) | 匹配任何不匹配指定模式的项目(extglob) |
通配符的简单使用示例
假如当前目录中只有这几个非隐藏文件,如下所示:
$ ls
a.jpg b.gif c.png d.pdf ee.pdf
-
简单通配符示例
下面我们进行简单的模式匹配
$ ls *.jpg a.jpg $ ls ?.pdf d.pdf $ ls [ab]* a.jpg b.gif
-
扩展通配符示例
扩展通配符的模式列表([pattern-list])中的各项由
|
分割。扩展通配符帮助您轻松解决许多问题,否则,需要相当多的丑陋的黑客行为。
想要使用扩展的 glob 进行匹配,首先要确认是否已经启用了 extglob 选项:
$ shopt | grep extglob extglob on
on 说明已经启用该选项,如果是 off,则使用以下命令启用
$ shopt -s extglob
如果已经启用该选项了,则可以使用扩展通配符进行匹配:
# 匹配指定模式的零个或一个 $ ls ?(*.jpg|*.gif) a.jpg b.gif # 除了 jpg 和 gif 以外 $ ls !(*.jpg|*.gif) c.png d.pdf ee.pdf # 删除除了匹配到的 jpg 以外的文件 $ rm !(*.jpg) # 复制除了 04 开头的 MP3 以外的所有文件到 /mnt $ cp !(04*).mp3 /mnt
再一些示例:
$ ls *.pdf ee.pdf e.pdf .pdf $ ls ?(e).pdf # 允许零个或一个 e e.pdf .pdf $ ls *(e).pdf # 允许零个或更多 e ee.pdf e.pdf .pdf $ ls +(e).pdf # 允许一个或更多 e ee.pdf e.pdf $ ls @(e).pdf # 只允许一个 e e.pdf
通配符匹配一些重要细节
通过简单的使用,看起来比较简单。但是其实还是有一些重要的细节问题。
星号 *
星号(*
)通配符匹配任意字符串,包括 null(空) 字符串。在启用了 globstar
shell 选项,并且 *
用在路径扩展环境时,那么,相连两个星号(**
) 将匹配所有文件和零个或多个目录以及子文件夹。如果两个星号跟在斜杠后面(/**
),那么这只能匹配目录和子目录。这段描述来自 bash man 页面。
这是当前目录现状:
# tree
.
├── anaconda-ks.cfg
└── test
├── filea
├── fileA
├── fileb
├── fileB
├── filec
├── fileC
├── filed
├── fileD
└── subdir
├── file1
├── file2
├── file3
├── file4
├── file5
└── file6
# 其实当前目录中还有一些隐藏文件没有显示出来。
# ls -a
. .bash_history .bashrc .cshrc .tcshrc
.. .bash_logout .cache .lesshst test
anaconda-ks.cfg .bash_profile .config .local .viminfo
下面使用两个星号示例
# ls **
anaconda-ks.cfg
test:
filea fileA fileb fileB filec fileC filed fileD subdir
# 使用 /** 这种语法
]# ls ./**
./anaconda-ks.cfg
./test:
filea fileA fileb fileB filec fileC filed fileD subdir
# ls test/**
test/filea test/fileb test/filec test/filed
test/fileA test/fileB test/fileC test/fileD
test/subdir:
file1 file2 file3 file4 file5 file6
从以上结果可以看到,tree
和 *
都不能显示或匹配以 .
开头的隐藏文件。
在 man 7 glob 帮助中,有一段这样的描述:如果文件名以 .
开头的,那么,点 .
字符必须显示地进行匹配。例如,rm *
不会删除 .profile 文件,tar c *
也不会归档所有文件,而使用 tar c .
更好。
想要列出当前目录中所有的隐藏文件,使用下面的格式并不是我们想要的:
# ls -d .*
. .bash_logout .cache .lesshst .viminfo
.. .bash_profile .config .local
.bash_history .bashrc .cshrc .tcshrc
这是因为任何目录中都有 .
(当前目录) 和 ..
(上一级目录)。
使用以下格式是可以的:
# ls -d .[!.]*
.bash_history .bashrc .cshrc .tcshrc
.bash_logout .cache .lesshst .viminfo
.bash_profile .config .local
想要列出当前目录中所有文件和文件夹,但不会显示 .
和 ..
# ls -d * .[^.]*
想要列出所有非隐藏的目录
ls -d /etc/*/
[…] 表达式
[…] 这种表达式有几点比较特殊:
-
如果在
[
后面紧跟的不是!
或^
,则会匹配中括号中出现的任何单个字符。否则,就是取补集,即会匹配非中括号中出现的任意单个字符。 -
中括号中字符串不能为空。因此,只要
]
出现在中括号的第一个位置,那么]
可以允许出现在其中。例如,[][!]
匹配这三个字符:[
,]
和!
。 -
想要表达一个范围,可以使用这种简便的格式,即在两个字符之间用
-
分开。例如[a-fA-F0-9]
等价于[abcdefABCDEF0123456789]
。通过把-
放在中括号的第一个或最后一个位置,就可以匹配字面上的自己。例如,[]-]
匹配两个字符:]
和-
。[-.0]
会匹配三个字符:-
,.
,0
。 -
在实际使用您可能遇到与想象不一样的情况,例如在当前目录中有以下文件和一个子目录 subdir:
]# ls filea fileA fileb fileB filec fileC filed fileD subdir ]# ls file[a-c] filea fileA fileb fileB filec
您会发现
[a-c]
匹配的等价于[aAbBc]
,而不是想象的[abc]
。其原因是范围表达式中的字符排序次序是由当前的locale
(区域设置,涉及语言、地区和编码) 和已经设置过的LC_COLLATE
或LC_ALL
shell 变量的值确定的。范围
[X-Y]
表示的是由当前排序顺序中位于X
和Y
(包括X
和Y
)之间的所有字符组成,而该排序顺序则由当前的locale
(区域设置)中的LC_COLLATE
类别的值决定。当前 locale 的值查询如下:
]# locale LANG=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_PAPER="en_US.UTF-8" LC_NAME="en_US.UTF-8" LC_ADDRESS="en_US.UTF-8" LC_TELEPHONE="en_US.UTF-8" LC_MEASUREMENT="en_US.UTF-8" LC_IDENTIFICATION="en_US.UTF-8" LC_ALL=
想要得到传统的范围表达式解释,让
[a-d]
等价于[abcd]
,请把LC_ALL
shell 变量的值设置为C
,或者,启用globasciiranges
shell 选项。在设置以上这些shell变量时,其优先级是不同的,具体为:“LC_ALL > LC_* > LANG”。]# LC_ALL=C ]# ls file[a-c] filea fileb filec
这里有一篇关于locale的解释供参考
-
命名字符类
为了减少以上这种因为国际化而造成的不便,可以使用已经设置好的字符类:
字符类 说明 [:alnum:] 所有字母和数字 [:alpha:] 所有字符,包括大小写 [:blank:] 所有水平空白字符 [:cntrl:] 所有控制字符(不可打印) [:digit:] 所有数字,等价于 [0-9] [:graph:] 所有图形字符 [:lower:] 所有小写字符 [:print:] 所有可打印字符 [:punct:] 所有标点符号 [:space:] 所有水平和垂直空格 [:upper:] 所有大写字符 [:xdigit:] 所有十六进制字符 注意:在使用中要采用这种格式:
[[:alnum:]]
]# ls file[[:lower:]] filea fileb filec filed
这种命令字符类在使用时可以在这基础上进行添加其他字符:
]# ls file[[:alnum:]_] # 这样就可以在数字字符基础上增加 _ ,这就与对变量名的要求一致了。
路径中的 /
按理说,通配符匹配会分别应用于路径名的各个组件,但是路径中的 /
不能被 ?
或 *
通配符或者像 [.-0]
这样的范围所匹配。显示包含 /
字符的范围在语法上是不正确的。(POSIX要求语法上错误的模式保持不变。)
# ls subdir[/]file*
ls: cannot access 'subdir[/]file*': No such file or directory
结论
通配符使用频繁,但是要掌握到位还是下一些功夫的。