Bash extglob反向模式匹配内部原理及异常命令解析
当前目录文件列表
-rw-r--r-- 1 kazama kazama 0 Feb 16 08:50 london_july_2001_001.jpeg -rw-r--r-- 1 kazama kazama 0 Feb 16 08:50 london_march_2004_002.png -rw-r--r-- 1 kazama kazama 0 Feb 16 08:50 newyork_dec_2012_005.jpeg -rw-r--r-- 1 kazama kazama 0 Feb 16 08:50 paris_sept_2007_003.jpg
需求与问题
需求是过滤掉所有以paris开头的图片文件。使用ls -l !(paris*)能得到预期结果,但ls -l !(paris)*.*(jpeg|jpg|png)和ls -l !(paris)*这两个命令却会显示全部4个文件,核心疑问是Bash如何解析这些extglob语法,是否分段匹配。
解析逻辑
Bash的扩展通配符(extglob)是以整个模式为单位匹配文件名,而非拆分后分段执行匹配,具体拆解如下:
!(paris*)的正确匹配
这个模式的含义是「匹配任何不以paris开头的文件名」,paris*作为一个整体被排除,因此能精准过滤掉所有paris开头的文件,符合预期。!(paris)*的问题所在!(paris)的含义是「匹配任何不等于精确字符串paris的内容」——它可以匹配空字符串、parisx这类包含paris但不全等于paris的字符串,甚至任意长度的非paris字符串。
当匹配paris_sept_2007_003.jpg时,Bash会将文件名拆分为「空字符串」(匹配!(paris),因为空字符串≠paris)和「整个文件名内容」(匹配后面的*),因此整个模式匹配成功,导致该文件被错误包含。!(paris)*.*(jpeg|jpg|png)的问题同理
同样的逻辑,!(paris)可以匹配空字符串,后续的*.*(jpeg|jpg|png)会匹配整个文件名,最终paris开头的图片文件也会被匹配到。
正确的写法
如果要实现「过滤掉paris开头的图片文件」,正确的extglob模式应该是:
ls -l !(paris*).*(jpeg|jpg|png)
这里!(paris*)作为整体排除所有paris开头的文件,再通过.*(jpeg|jpg|png)限定图片后缀,就能得到符合需求的结果。
内容的提问来源于stack exchange,提问作者Sking

