李守中

Bash 7 模式拓展

Table of Contents

1 模式扩展 globbing

Shell 收到用户指令后,依据空格,将输入拆分成一个个的词元 (token)。然后 Shell 会根据词元中所含的特殊字符的语义,将这些特殊字符扩写为普通字符,形成最终命令。最后执行最终命令。

这种特殊字符的扩展,称为模式扩展 (globbing)。其中有些用到通配符,又称为通配符扩展 (wildcard expansion)。

Bash 先进行 token 扩展,再执行命令。因此,扩展的结果是由 Bash 负责的,与所要执行的命令无关。命令本身并不存在参数扩展,收到什么参数就原样执行。

模式扩展早于正则表达式出现,可以看作是原始的正则表达式。所以,模式扩展的功能虽没有正则表达式强大灵活,但胜在简单方便。

Bash 允许用户关闭扩展:

$ set -o noglob
# 或者
$ set -f

打开扩展:

$ set +o noglob
# 或者
$ set +f

Bash 一共提供 8 种扩展:

  1. ~ 字符扩展。
  2. ? 字符扩展。
  3. * 字符扩展。
  4. [] 扩展。
  5. {} 扩展。
  6. 变量扩展。
  7. 子命令扩展。
  8. 算术扩展。
  9. 字符类扩展。

? * 扩展属于路径扩展,只有路径存在的前提下,才会发生扩展。如果文件不存在,扩展就不会发生。

2 ~ 字符扩展

2.1 ~ 与 ~/dir

~ 会自动扩展成当前用户的主目录:

$ echo ~
/home/lsz
$ echo ~/foo
/home/lsz/foo

2.2 ~user

~user 表示根据用户 user 的名称,返回该用户的主目录:

$ echo ~foo
/home/foo

$ echo ~root
/root

如果 ~useruser 是不存在的用户名,则波浪号扩展不起作用:

$ echo ~nonExistedUser
~nonExistedUser

2.3 ~+

~+ 会扩展成当前所在的目录,等同于 pwd 命令。

3 ? 字符扩展

? 表示路径中的单个字符,不包括空字符。

# 存在文件 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt

多个 ? 连用以匹配多个字符:

# 存在文件 a.txt b.txt 和 ab.txt
$ ls ??.txt
ab.txt

# 当前目录文件夹结构:
# ./
# ├── aa
# │   └── aa_file.txt
# ├── ab
# │   └── ab_file.txt
# └── ac
$ ls ./a?
./aa:
aa_file.txt

./ab:
ab_file.txt

./ac:

4 星号 * 字符扩展

* 代表路径中 0 个或多个字符,但不匹配隐藏文件 (以 . 开头的文件)。

# 存在文件 a.txt b.txt 和 ab.txt
$ ls a*.txt
a.txt ab.txt

$ ls *b*
b.txt ab.txt

如果要匹配隐藏文件,需要写成 .* 。如果要匹配隐藏文件,同时要排除 ... 这两个特殊的隐藏文件,可以与方括号扩展结合使用,写成 .[!.]*

$ echo .*
$ echo .[!.]*

要匹配子目录中的内容,就要在路径上写 * 匹配,有几层子目录,就必须写几层星号:

# 当前目录下的某个子目录内有一个 a.txt
# 无效的写法
$ ls *.txt

# 有效的写法
$ ls */*.txt

Bash 4.0 引入了一个参数 globstar ,当该参数打开,允许用 ** 来匹配零个或多个子目录。因此, **/*.txt 可以匹配顶层目录和任意深度子目录内的 *.txt 文件。详细请看 shopt 命令的笔记。

5 [] 扩展

5.1 基本形式

[] 中的任一字符出现一次:

# 存在文件 a.txt、b.txt
$ ls [ab].txt
a.txt b.txt

# 只有 a.txt
$ ls [ab].txt
a.txt

如果要匹配 [ 字符,可以放在方括号内,比如 [[aeiou]

如果要匹配连字号 - ,只能放在 [] 内的开头或结尾,比如 [-aeiou][aeiou-]

5.2 [^...] 和 [!...]

[^...][!...] 两种变体等价,表示匹配不在 [] 里面的字符。比如 [^abc][!abc] 表示匹配除 abc 以外的字符。

# 存在 aaa aba bbb 三个文件
$ ls ?[!a]?
aba bbb

5.3 [start-end] 形式

[start-end] 表示匹配一个连续的范围。比如 [a-c] 等同于 [abc][0-9] 匹配 [0123456789]

# 存在文件 a.txt、b.txt 和 c.txt
$ ls [a-c].txt
a.txt
b.txt
c.txt

# 存在文件 report1.txt、report2.txt 和 report3.txt
$ ls report[0-9].txt
report1.txt
report2.txt
report3.txt

常用简写:

  • [0-9]: 所有数字。
  • [a-zA-Z]: 所有字母。
  • [A-Z]: 所有大写字母。
  • [a-z]: 所有小写字母。
  • [a-zA-Z0-9]: 所有字母与数字。
  • program.[co]: 文件 program.c 与文件 program.o

[!start-end] 表示匹配不属于这个范围的字符。比如 [!a-zA-Z] 匹配非英文字母的字符。

# 存在文件 report1.txt、report2.txt 和 report3.txt
$ echo report[!1–3].txt
report4.txt report5.txt

6 {} 扩展

6.1 基本形式

{} 内各个值之间使用 , 分隔,且逗号前后不能有空格,表示分别用 {} 内的各个值替换表达式整体。比如 a{1,2,3} 扩展成 a1 a2 a3

$ echo {1,2,3}
1 2 3

$ echo d{a,e,i,u,o}g
dag deg dig dug dog

逗号 , 前面可以没有值,表示插入一个空的扩展项:

$ echo a.log{,.bak}
a.log a.log.bak

大括号 {} 表达式可以嵌套:

$ echo {j{p,pe}g,png}
jpg jpeg png

可与其他模式联用,并总优先于其他扩展:

$ echo /bin/{cat,b*}
/bin/cat /bin/b2sum /bin/base32 /bin/base64 ... ...

大括号 {} 表达式可以用于多字符模式匹配,中括号 [] 表达式只能匹配单字符。

$ echo {pig,dog}
pig dog

不论结果如何,大括号 {} 表达式扩展都会发生。

$ ls {a,b,c}.txt
ls: cannot access 'a.txt': No such file or directory
ls: cannot access 'b.txt': No such file or directory
ls: cannot access 'c.txt': No such file or directory

6.2 {start..end} 形式

6.2.1 基本用法

{start..end} 表示扩展成一个连续序列。比如, {a..z} 可以扩展成所有的小写英文字母:

$ echo {a..c}
a b c

$ echo d{a..d}g
dag dbg dcg ddg

这种形式支持逆序:

$ echo {c..a}
c b a

$ echo {5..1}
5 4 3 2 1

如果整数前面有前导 0,扩展输出的每一项都有前导 0。

$ echo {01..5}
01 02 03 04 05

$ echo {001..5}
001 002 003 004 005

遇到无法理解的形式,原样输出:

$ echo {a1..3c}
{a1..3c}

多个简写形式连用,会有循环处理的效果。

$ echo {a..c}{1..3}
a1 a2 a3 b1 b2 b3 c1 c2 c3

可以嵌套使用,形成复杂的扩展:

$ echo .{mp{3..4},m4{a,b,p,v}}
.mp3 .mp4 .m4a .m4b .m4p .m4v

6.2.2 常见用法

新建一系列目录:

$ mkdir {2007..2009}-{01..12}

另一个常见用途,是直接用于for循环:

for i in {1..4}
do
  echo $i
done

6.3 {start..end..step} 形式

{start..end..step} 可以指定扩展的步长:

$ echo {0..10..2}
0 2 4 6 8 10

7 变量扩展

Bash 将 $ 开头的词元视为变量,将其扩展成变量值。

$ echo $SHELL
/bin/bash

变量名也可以放在 ${} 里面。

$ echo ${SHELL}
/bin/bash

${!string*}${!string@} 返回所有匹配给定字符串 string 的变量名。

# 扩展成所有以S开头的变量名
$ echo ${!S*}
SECONDS SHELL SHELLOPTS SHLVL SSH_AGENT_PID SSH_AUTH_SOCK

8 子命令扩展

$(...) 可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值。

$ echo $(date)
Tue Jan 28 00:01:13 CST 2020

还有另一种较老的语法,子命令放在反引号之中,也可以扩展成命令的运行结果。

$ echo `date`
Tue Jan 28 00:01:13 CST 2020

$(...) 可以嵌套,比如 $(ls $(pwd))

9 算术扩展

$((...)) 可以扩展成整数运算的结果。

$ echo $((2 + 2))
4

10 字符类扩展

[[:class:]] 表示一个字符类,扩展成某一类特定字符之中的一个:

  • [[:alnum:]]: 匹配任意英文字母与数字
  • [[:alpha:]]: 匹配任意英文字母
  • [[:blank:]]: 空格和 Tab 键。
  • [[:cntrl:]]: ASCII 码 0-31 的不可打印字符。
  • [[:digit:]]: 匹配任意数字 0-9。
  • [[:graph:]]: A-Z、a-z、0-9 和标点符号。
  • [[:lower:]]: 匹配任意小写字母 a-z。
  • [[:print:]]: ASCII 码 32-127 的可打印字符。
  • [[:punct:]]: 标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
  • [[:space:]]: 空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
  • [[:upper:]]: 匹配任意大写字母 A-Z。
  • [[:xdigit:]]: 16进制字符(A-F、a-f、0-9)。

11 各扩展的匹配次数

量词语法可以用来控制模式匹配的次数。它只有在 Bash 的 extglob 参数打开的情况下生效。下面的命令可以查询:

$ shopt extglob
extglob        	on

如果extglob参数是关闭的,可以用下面的命令打开。

$ shopt -s extglob

量词语法有下面几个。

  • ?(pattern-list): 匹配 0 个或 1 个模式。
  • *(pattern-list): 匹配 0 个或多个模式。
  • +(pattern-list): 匹配 1 个或多个模式。
  • @(pattern-list): 匹配 1 个模式。
  • !(pattern-list): 匹配给定模式以外的任何内容。
# ?(def) 匹配零个或一个 def
$ ls abc?(def)
abc abcdef
# 匹配一个 .txt 或 .php
$ ls abc+(.txt|.php)
abc.php abc.txt


Last Update: 2023-08-30 Wed 14:47

Contact: [email protected]     Generated by: Emacs 27.1 (Org mode 9.3)

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议