Bash 中的模式匹配
通配符已经存在很久了。 有些人甚至声称它们出现在古埃及的象形文字中。通配符允许您简洁地指定 一个匹配一组文件名的模式(例如,*.pdf获取所有 PDF 文件的列表)。通配符通常也称为 glob 模式(或使用它们时,称为“globbing”)。但是 glob 模式的用途不仅仅是生成有用的文件名列表。bash 手册页将 glob 模式简单地称为“模式匹配”。
首先,让我们快速回顾一下 bash 的 glob 模式。除了广为人知的简单通配符之外,bash 还具有扩展 globbing,它添加了额外的功能。这些扩展功能通过以下方式启用extglob选项。
模式 | 描述 |
---|---|
* | 匹配零个或多个字符 |
? | 匹配任何单个字符 |
[...] | 匹配集合中的任何字符 |
?(patterns) | 匹配零个或一个模式出现次数 (extglob) |
*(patterns) | 匹配零个或多个模式出现次数 (extglob) |
+(patterns) | 匹配一个或多个模式出现次数 (extglob) |
@(patterns) | 匹配一个模式出现次数 (extglob) |
!(patterns) | 匹配任何与模式不匹配的内容 (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
$ shopt -s extglob # turn on extended globbing
$ ls ?(*.jpg|*.gif)
a.jpg b.gif
$ ls !(*.jpg|*.gif) # not a jpg or a gif
c.png d.pdf ee.pdf
当首次使用扩展 globbing 时,它们中的许多似乎并没有像我最初认为的那样工作。例如,在我看来,给定a.jpg,模式?(*.jpg|a.jpg)应该不匹配,因为a.jpg匹配了两个模式,并且?是“零个或一个”,对吗?错了。 我的困惑是由于对描述的误读:不是文件名只能匹配一次,而是模式只能匹配一次。从正则表达式的角度来考虑
Glob | 正则表达式等效项 | 描述 |
---|---|---|
?(patterns) | (regex)? | 匹配一个可选的正则表达式 |
*(patterns) | (regex)* | 匹配零个或多个正则表达式出现次数 |
+(patterns) | (regex)+ | 匹配一个或多个正则表达式出现次数 |
@(patterns) | (regex) | 匹配正则表达式(一次出现) |
所以,例如
$ ls *.pdf
ee.pdf e.pdf .pdf
$ ls ?(e).pdf # zero or one "e" allowed
e.pdf .pdf
$ ls *(e).pdf # zero or more "e"s allowed
ee.pdf e.pdf .pdf
$ ls +(e).pdf # one or more "e"s allowed
ee.pdf e.pdf
$ ls @(e).pdf # only one e allowed
e.pdf
当我在比较 glob 模式和正则表达式时,有一个重要的点需要指出,这可能不是立即可见的:glob 模式只是在 bash 中进行通用模式匹配的另一种语法。并且您可以在许多不同的地方使用它们
- 在==在 bash 的[[ expr ]]表达式中。
- 在case命令的模式中。
- 在参数扩展中 (%, %%, #, ##, /, //).
以下示例在 if 语句的表达式中使用模式匹配来测试变量的值是否为“something”或“anything”
$ shopt +s extglob
$ a=something
$ if [[ $a == +(some|any)thing ]]; then echo yes; else echo no; fi
yes
$ a=anything
$ if [[ $a == +(some|any)thing ]]; then echo yes; else echo no; fi
yes
$ a=nothing
$ if [[ $a == +(some|any)thing ]]; then echo yes; else echo no; fi
no
以下示例在 case 语句中使用模式匹配来确定文件是否为图像文件
shopt +s extglob
for f in $*
do
case $f in
!(*.gif|*.jpg|*.png)) # ! == does not match
echo "Not an image: $f"
;;
*)
echo "Image: $f"
;;
esac
done
$ bash script.sh a.jpg b.gif c.png d.pdf e.pdf
Image: a.jpg
Image: b.gif
Image: c.png
Not an image: d.pdf
Not an image: e.pdf
在上面的示例中,模式!(*.gif|*.jpg|*.png)如果文件名不是 gif、jpg 或 png,则会匹配。
以下示例在%%参数扩展中使用模式匹配来删除所有图像文件的扩展名
shopt -s extglob
for f in $*
do
echo ${f%%*(.gif|.jpg|.png)}
done
$ bash script.sh a.jpg b.gif c.png d.pdf e.pdf
a
b
c
d.pdf
e.pdf
我最近才意识到一个功能是,您可以一举完成上述操作:如果您使用“*”或“@”作为变量名,则转换将一次对所有命令行参数完成。[给自己提个醒:以后总是要阅读段落的后半部分]
shopt -s extglob
echo ${*%%*(.gif|.jpg|.png)}
$ bash script.sh a.jpg b.gif c.png d.pdf e.pdf
a b c d.pdf e.pdf
这也适用于数组
shopt -s extglob
array=($*)
echo ${array[*]%%*(.gif|.jpg|.png)}
$ bash script.sh a.jpg b.gif c.png d.pdf e.pdf
a b c d.pdf e.pdf
这里最大的收获是停止将通配符视为仅用于获取文件名列表的机制,而开始将它们视为 glob 模式,这些模式可用于在 bash 脚本中进行通用模式匹配。将 glob 模式视为另一种语言的正则表达式。
在我的文章中找到的任何代码都应被视为按以下方式许可
# Copyright 2019 Mitch Frazier <mitch -at- linuxjournal.com>
#
# This software may be used and distributed according to the terms of the
# MIT License or the GNU General Public License version 2 (or any later version).