为什么要写这篇文章
我相信很多人和我一样,天天在使用linux的shell命令,也大概知道有通配符这么个概念,不过比较模糊。平时也会使用简单的通配符,也许还知道有正则表达式的存在,甚至不清楚两者的区别,往往搞混淆。
我也是同样的问题,从来没有好好地静下心来总结这些东西。其实学习东西还是要踏实,这样才能发挥工具最大的作用,不是吗?本篇文章就shell的通配符来做个总结,方便以后自己查找记忆。注意,这里的shell我指的是bash,因为我几乎就没用过其它的shell,哈哈。
通配符的概念
如果查阅bash的手册或者man文档,我们发现根本压根没有通配符(wildcard)这个字眼,是不是很奇怪呢?进一步查看,转而发现这个通配符概念是路径扩展(英文pathname expansion,或者是文件名扩展(filename expansion))功能中的模式匹配(pattern matching)功能。是不是有点意外呢?
哦,原来在bash中存在很多种形式的扩展(expansion),而路径扩展(或者说文件名扩展)只是其中之一而已,了解这点尤为关键。
那么bash中具体有哪些扩展呢?我们不妨列出来,如下:
- brace expansion (花括号扩展)
- tilde expansion (波浪号扩展)
- parameter and variable expansion (参数和变量扩展)
- arithmetic expansion (算术扩展)
- command substitution (命令置换)
- word splitting (单词分割)
- filename expansion (文件名扩展)
- process substitution(进程替换)
好,本篇文章强调的通配符概念就是文件名扩展中的模式匹配知识点。OK,那么现在我们的问题就转变成了什么是bash的文件名扩展功能?什么是文件名扩展功能中的模式匹配(pattern matching)?bash又是如何处理这些功能的?
很好理解,就是bash在扫描命令行参数(不了解命令行参数的组成部分的童鞋可以参考文章《学会使用getopt函数》)时会注意操作数(Operands)部分是否有*,?,和[这些特殊模式字符(special pattern characters)。当它发现这些特殊模式字符时,会将它们视为要匹配的模式。通俗点说,就是bash发现参数部分有这些特殊字符时,会扩展这些符号,生成相应的已存在的文件名或者目录名,最后经过排序后传递给命令。
模式匹配
好了,到这里,我们应该了解到,平时说的所谓通配符在bash中的专业叫法是模式匹配(pattern matching)。
这里顺便提一下,这个模式匹配相比正则表达式而言,那是简单多了,有关正则表达式可以参阅这篇文章《正则表达式简单学习记录》。
接下来,就让我们详细地了解bash中的特殊模式字符吧。
首先把上图列出的特殊模式字符分为两类:?、*和[set]是最常见的特殊模式字符,在几乎所有的shell中都支持;而后5项是bash的扩展特殊字符,如果想使用,请确保extglob是设置着的,即shopt -s extglob.
好了,接下来结合实际的例子来学些这些特殊模式字符吧!首先是常见的特殊模式字符。
特殊模式字符?匹配任何的单一字符。因此如果目录下有whizprog.c、whizprog.log与whizprog.o这三个文件,那么表达式whizprog.?匹配的结果是whizprog.c和whizprog.o,但是whizprog.log不匹配。
特殊模式字符*是一个功能强大而且广为使用的通配符,它匹配于任何字符组成的字符串(包括空字符串)。表达式whiziprog.*匹配前面提到的三个文件;网页设计人员可以使用*.html表达式匹配他们的所有输入文件。
关于特殊模式字符*,bash有个选项globstar来控制连续两个星号的行为,即出现**的情况:
什么意思呢? 意思就是说选项globstar在disable(shopt -u globstar)情况下,**和*的行为是一样的(即**和*匹配当前目录下的所有文件名和目录名,**/和*/匹配当前目录下的所有目录名)。但是一旦enable(shopt -s globstar),那么**就会递归匹配所有的文件和目录,而**/仅会递归匹配所有的目录。通过以下实例可以看出实际区别:
[10:06:14@astrol:/etc/systemd]# pwd && ls
/etc/systemd
journald.conf logind.conf network resolved.conf system system.conf timesyncd.conf user user.conf
[10:06:28@astrol:/etc/systemd]# shopt globstar
globstar off
[10:06:36@astrol:/etc/systemd]#
[10:06:38@astrol:/etc/systemd]# echo *; echo **
journald.conf logind.conf network resolved.conf system system.conf timesyncd.conf user user.conf
journald.conf logind.conf network resolved.conf system system.conf timesyncd.conf user user.conf
[10:06:46@astrol:/etc/systemd]# echo */; echo **/
network/ system/ user/
network/ system/ user/
[10:06:53@astrol:/etc/systemd]# shopt -s globstar
[10:07:00@astrol:/etc/systemd]# echo *; echo **
journald.conf logind.conf network resolved.conf system system.conf timesyncd.conf user user.conf
journald.conf logind.conf network network/90-mac-for-usb.link network/99-default.link resolved.conf system system/basic.target.wants system/basic.target.wants/live-config.service system/clamav-daemon.service.d system/clamav-daemon.service.d/extend.conf system.conf system/dbus-org.freedesktop.ModemManager1.service system/dbus-org.freedesktop.nm-dispatcher.service system/default.target system/display-manager.service system/getty.target.wants system/getty.target.wants/getty@tty1.service system/graphical.target.wants system/graphical.target.wants/accounts-daemon.service system/hibernate.target.wants system/hibernate.target.wants/anacron-resume.service system/hybrid-sleep.target.wants system/hybrid-sleep.target.wants/anacron-resume.service system/iodined.service system/multi-user.target.wants system/multi-user.target.wants/anacron.service system/multi-user.target.wants/binfmt-support.service system/multi-user.target.wants/console-setup.service system/multi-user.target.wants/cron.service system/multi-user.target.wants/inetd.service system/multi-user.target.wants/irqbalance.service system/multi-user.target.wants/live-tools.service system/multi-user.target.wants/ModemManager.service system/multi-user.target.wants/networking.service system/multi-user.target.wants/NetworkManager.service system/multi-user.target.wants/nmbd.service system/multi-user.target.wants/openbsd-inetd.service system/multi-user.target.wants/pppd-dns.service system/multi-user.target.wants/remote-fs.target system/multi-user.target.wants/rsync.service system/multi-user.target.wants/rsyslog.service system/multi-user.target.wants/smartd.service system/multi-user.target.wants/smbd.service system/multi-user.target.wants/ssh.service system/multi-user.target.wants/vsftpd.service system/network-online.target.wants system/network-online.target.wants/networking.service system/samba-ad-dc.service system/sockets.target.wants system/sockets.target.wants/dm-event.socket system/sockets.target.wants/pcscd.socket system/sockets.target.wants/uuidd.socket system/sshd.service system/suspend.target.wants system/suspend.target.wants/anacron-resume.service system/sysinit.target.wants system/sysinit.target.wants/keyboard-setup.service system/sysinit.target.wants/lvm2-lvmetad.socket system/sysinit.target.wants/lvm2-lvmpolld.socket system/sysinit.target.wants/systemd-timesyncd.service system/syslog.service timesyncd.conf user user.conf
[10:07:05@astrol:/etc/systemd]#
[10:08:12@astrol:/etc/systemd]# echo */; echo **/
network/ system/ user/
network/ system/ system/basic.target.wants/ system/clamav-daemon.service.d/ system/getty.target.wants/ system/graphical.target.wants/ system/hibernate.target.wants/ system/hybrid-sleep.target.wants/ system/multi-user.target.wants/ system/network-online.target.wants/ system/sockets.target.wants/ system/suspend.target.wants/ system/sysinit.target.wants/ user/
[10:08:23@astrol:/etc/systemd]#
OK,接着看特殊模式字符[set],它与特殊模式字符?很相似,但允许匹配的更确切,把所有想要匹配的字符放在[ ]内,结果匹配其中的任一字符。可以使用波折号-表示范围,也可以使用第一个字符是!或者是^来表示反向匹配。举例如下:
whiziprog.[co]与whizprog.[a-z]匹配文件whizprog.c和whizprog.o,但不匹配文件whizprog.log。
[abc]和[a-c]匹配字符a、b或c
[!0-9]或者[^0-9]匹配任何一个非数字字符
[a-zA-Z0-9_-]匹配任何一个字母、任何一个数字、下划线或者破折号(假设ASCII环境下)。
好了,介绍完了几个常见的特殊模式字符后,我们来看看bash的几个扩展特殊模式字符。使用它们之前,请确保选项extglob是打开的(shopt -s extglob)。
怎么说呢,有了这几个扩展的特殊模式字符,就使得模式匹配有了点正则表达式的味道,自此模式匹配也有了重复、可选的功能了。
注意,这里的pattern-list可以是符号|隔开的模式,表示可选功能。好了,废话不多说,让我们看几个实际例子吧。
(1)列出当前目录下以“ab”或者“def”打头的JPEG或者GIF文件
ls +(ab|def)*.+(jpeg|gif)
(2)列出当前目录下匹配与正则表达式ab(2|3)+\.jpg相同匹配结果的所有文件
ls ab+(2|3).jpg
匹配到的文件名诸如ab2.jpg,ab222.jpg,ab3.jpg,ab333.jpg等。
(3)删除当前目录下除了以jpeg或者gif为后缀的文件,你可能会写出以下表达式:
rm -rf *!(.jpeg|.gif)
但是很遗憾,结果是不对的!这条命令会把当前目录下的所有文件删除。为什么呢?这是因为一开始的星号(*)匹配了所有的文件,当然就包括以.jpeg和.gif为后缀的文件。正确的写法如下:
rm -rf !(*.jpeg|*.gif)
(4)如何列出(1)中的反方向结果呢?很简单加个括号再取反就OK!
ls !(+(ab|def)*.+(jpeg|gif))
参考链接: