Linux Bash 通配符详解

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_COLLATELC_ALL shell 变量的值确定的。

    范围 [X-Y] 表示的是由当前排序顺序中位于XY(包括XY)之间的所有字符组成,而该排序顺序则由当前的 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

结论

通配符使用频繁,但是要掌握到位还是下一些功夫的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值