模式扩展
Shell 接受到用户输入的命令后,会根据输入内容通过单个空格进行分割,拆分成一个个词元(Token)。然后 Shell 会对词元中的期待的特殊字符进行扩展,扩展后在调用相应的命令。这样的特殊字符的扩展称之为模式扩展(globbing)。有些用到通配符的地方,称之为通配符扩展(wildcard expansion)。
再着重明一下:扩展是由 Bash 完成的,不是执行的命令扩展的。当 Bash 扩展完后在调用执行的命令,命令接收到什么参数就使用什么参数(参数可能是已经被扩展过的)。
可以发现,模式扩展 与 正则表达式的部分内容十分相似,二者的关系是什么样的?实际上,模式扩展更早与正则,但是功能没有正则表达式那么强大。优点是简单、方便,可以看为是最原始的正则。
Bash 一共提供八种扩展:
- 波浪线扩展
- ? 字符扩展
- * 字符扩展
- 方括号扩展
- 大括号扩展
- 变量扩展
- 子命令扩展
- 算术扩展
波浪线扩展
~
:扩展为当前登录用户的目录。
$ echo ~
/root
$ echo ~root
/root
$ echo ~+
/home/shellTest
第一个命令输出当前登录用户的目录路径,因为当前登录用户为root,所以第一个命令输出为 root 用户的目录路径。
第二个命令中的 ~root
这样的格式,会被扩展为:指定用户的目录路径。结合第一条命令可以说明,~
后不追加用户名,则默认为当前登录用户。
第三个命令中的 ~+
会被扩展为当前用户所在目录,即同 pwd
。
? 字符扩展
?
扩展为匹配任意单个字符
$ ls
test.sh
$ ls ?.sh
ls: cannot access ?.sh: No such file or directory
$ ls ????.sh
test.sh
* 字符扩展
*
扩展为匹配任意数量的任意字符(包含零个字符)
$ ls
ab.txt a.txt b.txt
$ ls *
ab.txt a.txt b.txt
$ ls a*.txt
ab.txt a.txt
ls *
无法查看隐藏文件( 文件名为.
开头的文件,即 touch .hidden.txt
)
如果需要查看隐藏文件,可通过:echo .*
$ touch .hidden.txt
$ echo .*
. .. .hidden.txt
其中 .
..
就是当前目录、当前目录的父目录。
如果想匹配子目录中的文件:
# 子目录有一个 a.txt
# 无效的写法
$ ls *.txt
# 有效的写法
$ ls */*.txt
# 如果孙子目录中有一个 a.txt
$ ls */*/*.txt
即有几层目录,就写几层星号。
方括号扩展
[]
扩展为匹配括号中的任意符,例:[aeiou]
,即匹配三个字母中的任意一个。
# 存在文件 a.txt、b.txt、ab.txt
$ ls [ab].txt
a.txt b.txt
两种取反变体,表示不匹配括号中的任意字符:
[^...]
[!...]
两种方式的功能是等价的。
# 存在 aaa.txt、bbb.txt、aba.txt 三个文件
$ ls ?[!b]?.txt
aaa.txt
注意:括号中匹配-
号时,-
号必须写在括号内部的头或尾处,否则无法生效。
# 存在 a-b.txt
$ ls ?[,-]?.txt
a-b.txt
$ ls ?[-,]?.txt
a-b.txt
$ ls ?[,-,]?.txt
ls: cannot access ?[c-d]?.txt: No such file or directory
范围匹配变体:[start-end]
[a-z]
:所有小写字母。[a-zA-Z]
:所有小写 & 大写字母。[a-zA-Z0-9]
:所有小写 & 大写字母 & 数字。[abc]*
:所有以a、b、c字符之一开头的文件名。BACKUP.[0-9][0-9][0-9]
:所有以BACKUP.开头,后面是三个数字的文件名。file[!1-5].txt
:匹配文件名最后一个字符非1-5的文件。
大括号扩展
{}
意为集合,表示为分别扩展大括号中的所有项(某个项也可以为一个{}
),项之间逗号分隔,并且前后逗号前后不能有空格,否则会被 Bash 理解为这是多个参数。
echo {A,B,C}
输出为:A、B、C。echo {j{p,pe}g,png}
,支持嵌套(先扩展嵌套)。输出为:jpg、jpeg、png。echo A{,B}C
,支持空值项。输出为:AC ABC。
与其他模式联用:
$ echo /bin/{cat,b*}
/bin/cat /bin/b2sum /bin/base32 /bin/base64 ... ...
# 等同于
$ echo /bin/cat;echo /bin/b*
上面例子,会先进行大括号扩展,然后进行*
扩展。所以在联用时,大括号扩展的优先级是较高的。
由于大括号扩展不是文件名扩展,所以它总是会扩展的。这与方括号扩展完全不同,如果匹配的文件不存在,方括号就不会扩展。但大括号扩展不会考虑文件是否存在,这一点要注意区分。
# 不存在 a.txt 和 b.txt
$ echo [ab].txt
[ab].txt
$ echo {a,b}.txt
a.txt b.txt
上面例子中,如果不存在a.txt和b.txt,那么[ab].txt就会变成一个普通的文件名,而{a,b}.txt可以照样扩展。
范围匹配变体:{start..end}
{a..e}
:a、b、c、d、e{0..3}
:0、1、2、3(正序){3..0}
:3、2、1、0(逆序){3..3}
:3(单值){a..3}
:{a..3}
(无法识别){08..11}
:08 09 10 11(补0)、{01..110}
:001、002、003 … 010 … 020 … 100、101、102 … 110(根据最大值的位数自动补0)。
范围匹配变体:{start..end..step}
{0..10..2}
:0 2 4 6 8 10
大括号连用:
# 循环的效果
$ echo {a..b},{1..2}
a,1 a,2 b,1 b,2
# 变更连接符
$ {a..b}-{1..2}
a-1 a-2 b-1 b-2
# 嵌套中的连用不会产生上面的循环效果
$ echo {{a..b},{1..2}}
a b 1 2
连用例子:根据年、月份创建目录
$ mkdir {2020..2022}-{01..12}
$ ls
2020-01 2020-03 2020-05 2020-07 ... 2022-04 2022-06 2022-08 2022-10 2022-12
大括号可以用于多字符的模式,方括号只能匹配单字符。
大括号扩展的常见用途为新建一系列目录。
$ mkdir {2007…2009}-{01…12}
变量扩展
Bash 将美元符号$开头的词元视为变量,将其扩展成变量值,后面的 变量 一节中会详细说明。
$ echo $SHELL
/bin/bash
# 可以放在括号中
$ echo ${SHELL}
/bin/bash
# 变量若不存在,什么都不输出
$ echo ${SHELL233}
子命令扩展
$(...)
可以扩展成另一个命令的运行结果,将括号内的命令的所有输出都会作为返回值。
$ date
Tue Oct 11 13:19:58 CST 2022
$ echo $(date)
Tue Oct 11 13:19:58 CST 2022
还有另一种较老的语法,子命令放在反引号之中,也可以扩展成命令的运行结果。
$ echo `date`
Tue Oct 11 13:19:58 CST 2022
$(...)
可以嵌套,比如:ls $(pwd)
,将 pwd 命令返回值作为 ls 命令的参数。
# 查看当前目录下的内容
$ ls $(pwd)
# 输出当前目录下的内容
$ echo $(ls $(pwd))
算术扩展
详细见 Blog:Shell 学习(6)Bash 的算术扩展
字符类
[[: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)。
$ echo [[:upper:]]*
上面命令输出所有大写字母开头的文件名。
字符类的第一个方括号后面,可以加上感叹号!,表示否定。比如,[![:digit:]]
匹配所有非数字。
$ echo [![:digit:]]*
上面命令输出所有不以数字开头的文件名。
shopt 命令
Shell option,这个命令可调整 Bash 的行为,开启或关闭一些 Bash 的功能项。
# 查看所有选项的状态
shopt
# 查询某个参数关闭还是打开
$ shopt [option]
# 查询某个参数关闭还是打开
$ shopt -q [option]
$ echo $?
不会直接输出查询结果,而是通过命令的执行退出码($?)表示查询结果。
如果状态为0,表示该参数打开;如果为1,表示该参数关闭。
$? 这个功能符号会在:Shell 学习(5)Bash 变量 中的特殊变量中详细说到。
# 打开某个参数
$ shopt -s [option]
# 关闭某个参数
$ shopt -u [option]
常用参数
1、dotglob
:扩展结果包含隐藏的文件(前面提到的.
开头的文件),默认情况下,扩展结果中是无法查询到隐藏文件的。
# 直接通过 * 扩展符查询
$ ls *
a.txt
# 开启 dotglob 项
$ shopt -s dotglob
# 在通过扩展符查看
$ ls *
a.txt .hidden.txt
不通过扩展符查看:
$ ls ./
a.txt
# 需要通过 -a 参数查看隐藏文件
$ ls -a
a.txt .hidden.txt
2、nullglob
:让通配符不匹配任何文件名时,返回空字符
默认情况下,通配符不匹配任何文件名时,会保持不变。
$ rm b*
rm: 无法删除'b*': 没有那个文件或目录
上面例子中,由于当前目录不包括b开头的文件名,导致b*
不会发生文件名扩展。也就是说 rm 命令接收到的参数就为b*
。
$ shopt -s nullglob
$ rm b*
rm: 缺少操作数
上面例子中,由于没有b*
匹配的文件名,所以rm b*
扩展成了 rm,导致报错变成了"缺少操作数"。
3、failglob
: 通配符不匹配任何文件名时,Bash 会直接报错,而不是让各个命令去处理
$ shopt -s failglob
$ rm b*
bash: 无匹配: b*
打开failglob以后,由于b*
不匹配任何文件名,Bash 直接报错了,不会在执行 rm 命令(注意错误信息的提示者)。
4、extglob
:使得 Bash 支持 ksh 的一些扩展语法。默认是打开的。
$ shopt extglob
extglob on
主要应用是支持量词语法,量词语法会在下面中详细说明。
5、nocaseglob
:让通配符扩展不区分大小写。
$ shopt -s nocaseglob
$ ls /windows/program*
/windows/ProgramData
/windows/Program Files
/windows/Program Files (x86)
6、globstar
:可以使得**
匹配零个或多个子目录。该参数默认是关闭的。
在上面的 * 字符扩展
小节中,如果想扩展多级目录,需要:
$ ls *.txt */*.txt */*/*.txt
a.txt sub1/b.txt sub1/sub2/c.txt
这是因为*
只匹配当前目录,如果要匹配子目录,只能一层层写出来。
打开globstar
参数以后,**
匹配零个或多个子目录:
$ shopt -s globstar
$ ls **/*.txt
a.txt sub1/b.txt sub1/sub2/c.txt
量词语法
需要先开启extglob
功能项:
$ shopt -s extglob
语法:
- ?(pattern-list):模式匹配零次或一次。
- *(pattern-list):模式匹配零次或多次。
- +(pattern-list):模式匹配一次或多次。
- @(pattern-list):只匹配一次模式。
- !(pattern-list):匹配给定模式以外的任何内容,即取非。
# 匹配零个或一个点
$ ls abc?(.)txt
abctxt abc.txt
# 匹配零个或一个def
$ ls abc?(def)
abc abcdef
# 匹配文件有且只有一个.txt或.php后缀名
$ ls abc@(.txt|.php)
abc.php abc.txt
# 匹配文件有一个或多个.txt后缀名
$ ls abc+(.txt)
abc.txt abc.txt.txt
# 匹配文件的名称最后一位不为b
$ ls a!(b).txt
a.txt ac.txt