声明:本文梳理自 GNU Bash 参考手册:https://www.gnu.org/software/bash/manual/bash.html;部分借鉴自通义千问 AI。
3.5 Shell 扩展(Shell Expansion)
在命令行被分割成 Token 之后,会进行扩展。共有以下七种类型的扩展:
- 大括号扩展(brace expansion)
- 波浪线扩展(tilde expansion)
- 参数和变量扩展(parameter and variable expansion)
- 命令替换(command substitution)
- 算术扩展(arithmetic expansion)
- 词分割(word splitting)
- 文件名扩展(filename expansion)
扩展的顺序是:大括号扩展;波浪线扩展、参数和变量扩展、算术扩展以及命令替换(这 4 种扩展按从左到右的顺序进行);词分割;以及文件名扩展。
在部分系统上,还有一种额外的扩展可用:进程替换。这一扩展与波浪线扩展、参数和变量扩展、算术扩展以及命令替换同时进行。
在执行了这些扩展之后,原始单词中存在的引号字符会被移除,除非这些引号本身也被引用过(引号移除)。
只有大括号扩展、词分割和文件名扩展能够增加扩展后的单词数量;其他类型的扩展都是将单个单词扩展为另一个单个单词。有如下两个特殊参数例外:"$@" 和 $* 的扩展(Shell|GNU Bash 参考手册:3.4 Shell 参数 中的 3.4.2 特殊参数),以及 "${name[@]}" 和 ${name[*]} 的扩展(详见 “6.7 数组”)。
在所有扩展完成后,将执行引号移除(quote removal)(详见 “3.5.9 引号剔除”)。
3.5.1 大括号扩展(Brace Expansion)
大括号展开(brace expansion)是一种可以生成任意字符串的机制。这种机制类似于文件名展开(详见 “3.5.8 文件名扩展”),但是生成的文件名不必实际存在。要进行括号展开的模式由一个可选的前导部分开始,接着是一系列逗号分隔的字符串或一对大括号内的一组序列表达式,最后是一个可选的后缀。前导部分被添加到每个包含在大括号内的字符串前面,然后将后缀附加到每个结果字符串之后,从左至右进行扩展。
例如:
echo file{1,2,3}.txt会生成file1.txt file2.txt file3.txt。这里的{1,2,3}就是括号中的内容,而file是前导部分,.txt是后缀部分。
大括号扩展可以嵌套。每个展开字符串的结果不会被排序,会保留从左到右的顺序。例如:
bash$ echo a{d,c,b}e
ade ace abe序列表达式的形式为 {x..y[..incr]},其中 x 和 y 可以是整数或字母,而 incr 是一个可选的增量,为整数。当提供的是整数时,该表达式会扩展为从 x 到 y(包括 x 和 y)之间的每个数字。提供的整数可以通过前缀 0 来强制每个项具有相同的宽度。如果 x 或 y 以零开头,shell 会尝试强制所有生成的项包含相同数量的数字,在必要时用零填充。当提供的是字母时,该表达式会根据默认的字符集设置扩展为从 x 到 y(包括 x 和 y)之间按字典顺序排列的每个字符。需要注意的是,x 和 y 必须是同一类型(整数或字母)。当提供了增量时,它将作为每项之间的差值使用。如果没有指定增量,默认增量是适当的 1 或 -1。
大括号展开在任何其他展开之前执行,并且结果中会保留对其他展开具有特殊意义的字符。它是纯文本的。Bash 不会对展开的上下文或大括号之间的文本进行任何语法解释。
一个格式正确的括号展开必须包含未被引用的开括号和闭括号,并且至少包含一个未被引用的逗号或一个有效的序列表达式。任何格式不正确的括号展开将保持不变。
在大括号展开中的 { 和 , 需要 \ 进行转义,以防止它们被视为括号表达式的一部分。为了避免与参数展开产生冲突,字符串 ${ 不会被视为适用于括号展开,并且会抑制括号展开直到遇到闭合的 }。
这种结构通常用作一种简写,当要生成的字符串的公共前缀较长时使用,例如:
mkdir /usr/local/src/bash/{old,new,dist,bugs}或
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}3.5.2 波浪线扩展(Tilde Expansion)
如果一个单词以未加引号的波浪线字符 ~ 开始,那么直到第一个未加引号的 / 之前的所有字符(如果没有未加引号的斜杠,则是全部字符)都被视为一个波浪线前缀(tilde-prefix)。如果波浪线前缀中的所有字符都没有被引用,那么波浪线之后的字符被视为一个可能的登录名(login name)。如果这个登录名为空字符串,那么波浪线将被 shell 变量 HOME 的值所替换。如果 HOME 未设置,那么将替换为执行 shell 的用户的家目录。否则,波浪线前缀将被与指定登录名关联的家目录所替换。
如果波浪线前缀是 ~+,则将使用 shell 变量 PWD 的值来替换这个波浪线前缀。如果波浪线前缀是 ~-,那么如果 shell 变量 OLDPWD 已被设置,则使用变量 OLDPWD 的值来替换这个波浪线前缀。
如果波浪线前缀中波浪线之后的字符是一个数字 N,这个数字可以被一个 + 或 - 可选地作为前缀,那么这个波浪线前缀将被目录栈中相应的元素所替换,就像通过 dirs 内置命令并以波浪线后跟随的字符作为参数调用时显示的一样(详见 “6.8 目录栈”)。如果波浪线前缀除了波浪线外仅由一个没有前置 + 或 - 的数字组成,则假定为带有 +。
如果登录名无效,或者波浪线展开失败,那么该单词将保持不变。
每个变量赋值都会被检查是否存在直接跟在 : 或第一个 = 之后的未加引号的波浪线前缀。在这些情况下,也会执行波浪线展开。因此,可以在对 PATH、MAILPATH 和 CDPATH 等变量的赋值中使用带有波浪线的文件名,shell 会赋予展开后的值。
下表显示了 Bash 如何处理未加引号的波浪线前缀:
波浪钱前缀 | 替换为 |
| 等价于 |
| 等价于 |
|
|
| 等价于 |
| 等价于 |
| 生成 |
| 生成 |
| 生成 |
当这些单词作为简单命令的参数出现时(详见 Shell|GNU Bash 参考手册:3.4 Shell 参数),Bash 还会对满足变量赋值条件的单词执行波浪线展开。但是,在 POSIX 模式下,除了上述声明命令之外,Bash 不会这样做。
3.5.3 Shell 参数扩展(Shell Parameter Expansion)
$ 符号引入参数扩展、命令替换或算术扩展。要扩展的参数名或符号可以被大括号包围,大括号是可选的,但它们的作用是保护要扩展的变量,使其不受紧随其后的可能被解释为名称一部分的字符的影响。
当使用大括号时,匹配的结束大括号是第一个未被 \ 转义、不在引号字符串内、且不位于嵌入的算术扩展、命令替换或参数扩展内的 }。
参数扩展的基本形式是 ${parameter},其中的 parameter 的值会被替换。参数是一个 shell 参数(详见 Shell|GNU Bash 参考手册:3.4 Shell 参数)或数组引用(详见 “6.7 数组”)。当参数是一个超过一位数的位置参数,或者参数后面跟着一个不应被解释为其名称一部分的字符时,则大括号是必需的。
如果参数的第一个字符是感叹号 !,并且该参数不是一个名称引用(nameref),则它引入了一层间接性。Bash 会使用通过展开参数剩余部分所形成的新值作为新的参数;然后这个新参数会被进一步展开,其值将用于后续的扩展过程中,而不是使用原始参数的扩展值。这被称为“间接扩展”。这个值将受到波浪线扩展、参数扩展、命令替换和算术扩展的影响。如果参数是一个名称引用,则此扩展会得到由参数引用的变量名,而不是执行完整的间接扩展。对此规则的例外情况是 ${!prefix*} 和 ${!name[@]} 的扩展,这些将在下文进行描述。为了引入间接性,感叹号必须紧跟在左大括号之后。
在下面的每个案例中,单词都会经过波浪线展开、参数展开、命令替换和算术展开。
当不执行子字符串扩展时,需要使用 :- 的形式,Bash 会检查参数是否未设置或为空。省略冒号则仅检查参数是否未设置。换句话说,如果包含了冒号,那么操作符将同时检查参数是否存在及其值是否非空;如果省略了冒号,操作符仅检查参数是否存在。
3.5.3.1 样例:${parameter:-word}
如果参数未设置或为空,则替换为 word 的展开结果。否则,替换为参数的值:
$ v=123
$ echo ${v-unset}
1233.5.3.2 样例:${parameter:=word}
如果参数未设置或为空,那么 word 的展开值将被赋给该参数。之后,该参数的值会被替换。但是,位置参数和特殊参数不能以这种方式进行赋值。
$ var=
$ : ${var:=DEFAULT}
$ echo $var
DEFAULT3.5.3.3 样例:${parameter:?word}
如果参数为空或未设置,那么 word 的展开值(或者如果没有提供 word,则是一个相应的消息)将被写入标准错误输出,并且如果 shell 不是交互式的,它将会退出。否则,该参数的值会被替换。
$ var=
$ : ${var:?var is unset or null}
bash: var: var is unset or null3.5.3.4 样例:${parameter:+word}
如果参数为空或未设置,则不进行任何替换;否则,将替换为 word 的展开值。
$ var=123
$ echo ${var:+var is set and not null}
var is set and not null3.5.3.5 样例:${parameter:offset} 和 ${parameter:offset:length}
这被称为子字符串展开。它会从参数值中由 offset 指定的字符开始,展开至多 length 个字符。如果参数是 @ 或 *,或者是由 @ 或 * 索引的数组,或者是关联数组的名字,那么结果将有所不同,具体如下所述。如果省略了 length,则它会展开为从参数值中由偏移量指定的字符开始到该值末尾的子字符串。length 和 offset 是算术表达式(详见 “6.5 Shell 算术”)。
如果 offset 计算结果为小于零的数字,那么这个值将被视为从参数值末尾开始计算的字符偏移量。如果 length 计算结果为小于零的数字,它会被解释为从参数值末尾开始计算的字符偏移量,而不是字符的数量,并且展开的部分是从 offset 到该结果之间的字符。请注意,负数的 offset 必须与冒号之间至少有一个空格分隔,以避免与 ‘:-’ 展开混淆。
这里有一些示例,说明了在参数和带下标的数组上进行子字符串展开的情况:
$ string=01234567890abcdefgh
$ echo ${string:7}
7890abcdefgh
$ echo ${string:7:0}
$ echo ${string:7:2}
78
$ echo ${string:7:-2}
7890abcdef
$ echo ${string: -7}
bcdefgh
$ echo ${string: -7:0}
$ echo ${string: -7:2}
bc
$ echo ${string: -7:-2}
bcdef
$ set -- 01234567890abcdefgh
$ echo ${1:7}
7890abcdefgh
$ echo ${1:7:0}
$ echo ${1:7:2}
78
$ echo ${1:7:-2}
7890abcdef
$ echo ${1: -7}
bcdefgh
$ echo ${1: -7:0}
$ echo ${1: -7:2}
bc
$ echo ${1: -7:-2}
bcdef
$ array[0]=01234567890abcdefgh
$ echo ${array[0]:7}
7890abcdefgh
$ echo ${array[0]:7:0}
$ echo ${array[0]:7:2}
78
$ echo ${array[0]:7:-2}
7890abcdef
$ echo ${array[0]: -7}
bcdefgh
$ echo ${array[0]: -7:0}
$ echo ${array[0]: -7:2}
bc
$ echo ${array[0]: -7:-2}
bcdef如果参数是 @ 或 *,那么结果是从偏移量开始的 length 个位置参数。负偏移量是相对于最大的最大位置参数计算的,即 -1 偏移量表示最后一个位置参数。如果 length 的值小于零的数,则会产生一个扩展错误。
下面的示例说明了如何使用位置参数来进行子字符串扩展:
$ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:7}
7 8 9 0 a b c d e f g h
$ echo ${@:7:0}
$ echo ${@:7:2}
7 8
$ echo ${@:7:-2}
bash: -2: substring expression < 0
$ echo ${@: -7:2}
b c
$ echo ${@:0}
./bash 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:0:2}
./bash 1
$ echo ${@: -7:0}如果参数是由 @ 或 * 作为下标索引的数组名称,那么结果是从 ${parameter[offset]} 开始的数组成员长度。负偏移量是相对于指定数组的最大索引加一来计算的。如果 length 计算出的数值小于零,则将出现一个扩展错误。
这些示例展示了如何使用索引数组进行子字符串扩展:
$ array=(0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h)
$ echo ${array[@]:7}
7 8 9 0 a b c d e f g h
$ echo ${array[@]:7:2}
7 8
$ echo ${array[@]: -7:2}
b c
$ echo ${array[@]: -7:-2}
bash: -2: substring expression < 0
$ echo ${array[@]:0}
0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${array[@]:0:2}
0 1
$ echo ${array[@]: -7:0}对关联数组(associative array)应用子字符串扩展会产生未定义的结果。
除非使用位置参数时下标从 1 开始外,其他情况下子字符串下标均从 0 开始的。如果偏移量是 0,并且使用了位置参数,那么会在列表前加上 $0。
3.5.3.6 样例:${!prefix*} 和 ${!prefix@}
展开名称以 prefix 开头的变量名,这些名称由 IFS 特殊变量的第一个字符分隔。当使用 @ 且扩展出现在双引号内时,每个变量名都会扩展成一个单独的词。
3.5.3.7 样例:${!name[@]} 和 ${!name[*]}
如果 name 是一个数组变量,那么会扩展为 name 数组变量的索引(键)列表。如果 name 不是数组,那么如果 name 已设置则扩展为 0,否则扩展为空。
当使用 @ 且扩展出现在双引号内时,每个键都会扩展成一个单独的词。
3.5.3.8 样例:${#parameter}
${#parameter} 将会扩展为 parameter 的字符数量。如果参数是 * 或 @,则被替换的值是位置参数的数量。如果参数是由 * 或 @ 作为下标的数组名称,则被替换的值是数组中的元素数量。如果参数是由负数作为下标的索引数组名称,该数字被视为相对于参数的最大索引加一来计算,因此负索引从数组末尾倒数,索引 -1 引用最后一个元素。
3.5.3.9 样例:${parameter#word} 和 ${parameter##word}
word 将被扩展以生成一个模式,并根据下面描述的规则进行匹配(详见 “3.5.8.1 模式匹配”)。如果模式与 parameter 的扩展值的 开头 相匹配,那么扩展的结果将是 parameter 的扩展值减去最短匹配模式(对于 # 的情况)或最长匹配模式(对于 ## 的情况)。如果 parameter 是 @ 或 *,则依次对每个位置参数应用模式移除操作,而扩展结果为处理后的列表。如果 parameter 是以 @ 或 * 作为下标的数组变量,则依次对数组中的每个成员应用模式移除操作,而扩展结果为处理后的列表。
3.5.3.10 样例:${parameter%word} 和 ${parameter%%word}
word 将被扩展以生成一个模式,并根据下面描述的规则进行匹配(详见 “3.5.8.1 模式匹配”)。如果模式与 parameter 的扩展值的 末尾 相匹配,那么扩展的结果将是 parameter 的扩展至减去最短匹配模式(对于 % 的情况)或最长匹配模式(对于 %% 的情况)。如果 parameter 是 @ 或 *,则依次对每个位置参数应用模式移除操作,而扩展结果为处理后的列表。如果 parameter 是以 @ 或 * 作为下标的数组变量,则依次对数组中的每个成员应用模式移除操作,而扩展结果为处理后的列表。
3.5.3.11 样例:${parameter/pattern/string}、${parameter//pattern/string}、${parameter/#pattern/string} 和 ${parameter/%pattern/string}
pattern 被扩展以生成一个模式,就如同在文件名扩展中一样。parameter 被扩展,并且其值与模式的最长匹配部分将被 string 替换。string 会经历波浪线扩展、参数和变量扩展、算术扩展、命令和进程替换以及引号移除。匹配是根据下面描述的规则进行的(详见 “3.5.8.1 模式匹配”)。
- 在
${parameter/pattern/string}中,只有第一个匹配项会被替换; - 在
${parameter//pattern/string}中,则所有与模式匹配的部分都将被string替换; - 在
${parameter/#pattern/string}中,必须与参数扩展值的开头相匹配; - 在
${parameter/%pattern/string}中,必须与参数扩展值的结尾相匹配。
如果 string 的扩展结果为空,则与模式匹配的部分将被删除。如果 string 本身为空,则与模式匹配的部分将被删除,并且模式后面的 / 可以省略。
如果使用 shopt 启用了 patsub_replacement shell 选项,那么 string 中任何未被引号包围的 & 都会被模式中匹配的部分所替换。这一功能旨在复制一个常见的 sed 用法。
对 string 的任何部分加引号都会阻止该引号部分在扩展时被替换,包括存储在 shell 变量中的替换字符串。反斜杠可以在 string 中转义 &;反斜杠会被移除以便允许替换字符串中出现字面的 &。如果 string 被双引号包围时,应当小心以避免反斜杠与双引号之间产生不必要的交互,因为在双引号内反斜杠有特殊的意义。
模式替换是在 string 扩展之后检查未被引号包围的 &,因此用户应确保他们希望按字面理解的 & 被正确地加引号,并确保希望被替换的任何 & 实例不被引号包围。
例如:
var=abcdef
rep='& '
echo ${var/abc/& }
echo "${var/abc/& }"
echo ${var/abc/$rep}
echo "${var/abc/$rep}"将生成四行 abc def。然而:
var=abcdef
rep='& '
echo ${var/abc/\& }
echo "${var/abc/\& }"
echo ${var/abc/"& "}
echo ${var/abc/"$rep"}将生成四行 & def。与模式移除操作符类似, "${var/abc/\& }" 会引用扩展后的字符,而 ${var/abc/"& "} 和 ${var/abc/"$rep"} 则不会,因为扩展是在一个不考虑任何外部双引号的上下文中进行的。
由于 \ 可以转义 &,它也可以在替换字符串中转义 \。这意味着 \\ 会在替换中插入一个字面的反斜杠,所以这两个 echo 命令:
var=abcdef
rep='\\&xyz'
echo ${var/abc/\\&xyz}
echo ${var/abc/$rep}会输出两个 \abcxyzdef。通常只在很少的场景下需要将 string 部分用双引号框柱。
如果启用了 nocasematch shell 选项(详见 4.3.2 中对 shopt 的描述),则匹配时将忽略字母字符的大小写。如果参数是 @ 或 *,则依次对每个位置参数应用替换操作,而扩展结果为处理后的列表。如果参数是以 @ 或 * 作为下标的数组变量,则依次对数组中的每个成员应用替换操作,而扩展结果为处理后的列表。
3.5.3.12 样例:${parameter^pattern}、${parameter^^pattern}、${parameter,pattern} 和 ${parameter,,pattern}
这种扩展修改 parameter 中字母字符的大小写。pattern 被扩展以生成一个模式,就如同在文件名扩展中一样。parameter 扩展值中的每个字符都与 pattern 进行比较,如果匹配,则其大小写会被转换。pattern 不应该试图匹配多于一个字符。
^ 操作符将与模式匹配的第一个字符从小写字母转换为大写,, 操作符将与模式匹配的第一个字符从大写字母转换为小写。^^ 和 ,, 扩展将扩展值中每个匹配的字符进行转换。如果省略了模式,它会被当作 ? 处理,这将匹配每一个字符。
如果参数是 @ 或 *,则依次对每个位置参数应用大小写修改操作,而扩展结果为处理后的列表。如果参数是以 @ 或 * 作为下标的数组变量,则依次对数组中的每个成员应用大小写修改操作,而扩展结果为处理后的列表。
3.5.3.13 样例:${parameter@operator}
扩展结果要么是 parameter 值的转换,要么是关于 parameter 本身的信息,这取决于 operator 的值。每个 operator 都是一个单字母:
操作符(operator) | 执行逻辑 |
| 将 |
| 将 |
| 将 |
| 将 |
| 将 |
| 将 |
| 将 |
| 将 |
| 由 |
| 将 |
如果参数是 @ 或 *,则该操作会依次应用于每个位置参数,而扩展结果为处理后的列表。如果参数是以 @ 或 * 作为下标的数组变量,则该操作会依次应用于数组中的每个成员,而扩展结果为处理后的列表。
3.5.4 命令替换(command substitution)
命令替换允许一个命令的输出代替该命令本身。当一个命令被如下方式括起来时,就会发生命令替换:
$(command)或
`command`Bash 通过在子 shell 环境中执行命令并用命令的标准输出替换命令替换来完成扩展,同时删除任何尾随的换行符。嵌入的换行符不会被删除,但它们可能在单词拆分过程中被移除。命令替换 $(cat file) 可以用等效但更快的 $(< file) 来替换。
当使用旧式的反引号形式的替换时,反斜杠保持其字面意义,除非它后面跟着 $、反引号(`)或 \。第一个不以前导 \ 出现的反引号终止命令替换。当使用 $(command) 形式时,括号之间的所有字符都构成命令;没有字符会被特殊处理。
命令替换可以嵌套。当使用反引号形式进行嵌套时,用反斜杠转义内部的反引号。
如果替换出现在双引号内,则不对结果执行单词拆分和文件名扩展。
3.5.5 算术扩展(Arithmetic Expansion)
算术扩展允许计算一个算术表达式并用结果进行替换。算术扩展的格式是:
$(( expression ))表达式会经历与它被双引号包围时相同的扩展,但是在表达式中的双引号字符不会被特殊处理,并且会被移除。表达式中的所有标记都会经过参数和变量扩展、命令替换以及引号移除。最终的结果将被视为待计算的算术表达式。算术扩展可以嵌套使用。
表达式的计算是根据以下规则进行的(详见 “6.5 Shell 算术”)。如果表达式无效,Bash 会向标准错误输出打印一条表示失败的消息,并且不会发生替换。
3.5.6 进程替换(Process Substitution)
进程替换允许使用文件名来引用一个进程的输入或输出。它采用以下形式:
<(list)或
>(list)这里的进程列表是异步运行的,它的输入或输出表现为一个文件名。这个文件名作为扩展结果被传递给当前命令作为参数。如果使用了 >(list) 的形式,那么写入到该文件的内容将会为列表提供输入。如果使用了 <(list) 的形式,则应该读取作为参数传递的文件以获取列表的输出。需要注意的是,在 < 或 > 和左括号之间不能有空格,否则该结构会被解释为重定向操作。在支持命名管道(FIFO)或使用 /dev/fd 方法命名打开文件的系统上支持进程替换。
当可用时,进程替换与参数和变量扩展、命令替换以及算术扩展同时执行。
3.5.7 词切割(Word Splitting)
shell 会对不在双引号内的参数扩展、命令替换和算术扩展的结果进行单词拆分的检查。
shell 将 $IFS 中的每个字符视为分隔符,并使用这些字符作为字段终止符来分割其他扩展的结果成单词。如果 IFS 未设置,或者它的值恰好是默认的 \t\n(空格、制表符、换行符),那么在前面扩展结果的开头和结尾处出现的 (空格)、\t(制表符)和 \n(换行符)序列将被忽略,而在非开头或结尾位置的任何 IFS 字符序列都将用作单词的分隔符。如果 IFS 的值不是默认值,那么只要空白字符(空格、制表符和换行符)出现在 IFS 的值中(即 IFS 空白字符),单词开头和结尾处的这些空白字符序列就会被忽略。IFS 中的任何非 IFS 空白字符,连同其相邻的 IFS 空白字符一起,都会作为字段的分隔符。一个 IFS 空白字符的序列也被视为一个分隔符。如果 IFS 的值为空,则不会发生单词拆分。
显式的空参数("" 或 '')会被保留,并作为空字符串传递给命令。未被引用的隐式空参数,即由没有值的参数扩展产生的,将被移除。如果一个没有值的参数在双引号内被展开,会产生一个空参数,这个空参数会被保留,并作为空字符串传递给命令。当一个被引用的空参数出现在一个非空扩展词中时,该空参数将被移除。也就是说,在单词拆分和空参数移除后,单词 -d'' 会变成 -d。
请注意,如果没有发生任何扩展,则不会执行拆分。
3.5.8 文件名扩展(Filename Expansion)
在单词分割后,除非设置了 -f 选项(详见 “4.3.1 内置 Set”),Bash 会扫描每个单词中是否包含字符 *、? 和 [。如果这些字符中的任何一个出现,并且没有被引号包围,那么该单词将被视为一个模式,并被替换为与该模式匹配的文件名列表,这些文件名按照字母顺序排列(详见 “3.5.8.1 模式匹配”)。如果没有找到匹配的文件名,并且 shell 选项 nullglob 没有启用,那么该单词保持不变。如果启用了 nullglob 选项并且没有找到匹配项,则该单词会被移除。如果设置了 failglob shell 选项,并且没有找到匹配项,则会打印错误消息并且不会执行命令。如果启用了 nocaseglob shell 选项,那么匹配时将忽略字母字符的大小写。
当模式用于文件名扩展时,位于文件名开头或紧跟在斜杠之后的字符 . 必须被显式匹配,除非设置了 shell 选项 dotglob。为了匹配文件名. 和 ..,即使设置了 dotglob,模式也必须以 . 开头(例如 .?)。如果启用了 globskipdots shell 选项,那么即使模式以 . 开始,文件名 . 和 .. 也永远不会被匹配。在不匹配文件名的情况下,. 字符不会被特别处理。
当匹配文件名时,/ 必须总是通过模式中的 / 来显式匹配,但在其他匹配上下文中,它可以由下面描述的特殊模式字符来匹配(详见 “3.5.8.1 模式匹配”)。
有关 nocaseglob、nullglob、globskipdots、failglob 和 dotglob 选项的描述,详见 “4.3.2 内置命令 shopt” 中对 shopt 的说明。
可以使用 GLOBIGNORE shell 变量来限制与模式匹配的文件名集合。如果设置了 GLOBIGNORE,那么每个同时匹配 GLOBIGNORE 中任一模式的匹配文件名将从匹配列表中移除。如果启用了 nocaseglob 选项,则与 GLOBIGNORE 中模式的匹配将不区分字母大小写。当 GLOBIGNORE 被设置为非空值时,文件名 . 和 .. 总是被忽略。然而,将 GLOBIGNORE 设置为非空值具有启用 dotglob shell 选项的效果,因此所有其他以 . 开头的文件名也将匹配。为了恢复忽略以 . 开头的文件名的旧行为,可以在 GLOBIGNORE 中包含 .* 作为其中一个模式。当取消设置 GLOBIGNORE 时,dotglob 选项会被禁用。
3.5.8.1 模式匹配(Pattern Matching)
除了下面描述的特殊模式字符外,模式中出现的任何字符都匹配其自身。模式中不能包含 NUL 字符。\ 用于转义其后的字符;在匹配时,这个转义用的 \ 会被丢弃。如果要按字面意思匹配特殊模式字符,必须对它们进行引用。
特殊模式字符具有以下含义:
*匹配任何字符串,包括空字符串。当启用了
globstarshell 选项时,若*用在文件名扩展上下文中,两个连续的*将作为单一模式将匹配所有文件和零个或多个目录及子目录。如果后面跟着一个/,两个连续的*只会匹配目录和子目录。
?匹配任意单个字符。
[...]匹配包含的任意一个字符。一对由连字符分隔的字符表示一个范围表达式;根据当前区域设置的排序顺序和字符集,所有位于这两个字符之间(包括这两个字符)的字符都将被匹配。如果
[后的第一个字符是!或^,则匹配任何未包含在内的字符。通过将-作为集合中的第一个或最后一个字符,可以匹配-。通过将]作为集合中的第一个字符,可以匹配]。范围表达式中字符的排序顺序以及包含在范围内的字符由当前区域设置及LC_COLLATE和LC_ALLshell 变量的值决定。例如,在默认的 C 区域设置中,
[a-dx-z]等同于[abcdxyz]。许多区域设置按照字典顺序对字符进行排序,在这些区域设置中[a-dx-z]通常不等同于[abcdxyz];例如它可能等同于[aBbCcDdxYyZz]。要获得在方括号表达式中范围的传统解释,你可以通过将LC_COLLATE或LC_ALL环境变量设置为值C来强制使用 C 区域设置,或者启用globasciirangesshell选项。在
[和]之间,可以使用语法[:类名:]来指定字符类(character class),其中类名是 POSIX 标准中定义的以下类别之一:alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit
字符类匹配属于该类的任何字符。其中
word字符类匹配字母、数字和_(下划线)。在
[和]之间,可以使用语法[=c=]指等价类(equivalence class),它匹配所有与字符 c 具有相同排序权重(根据当前区域设置定义)的字符。在
[和]之间,语法[.symbol. ]匹配排序符号 symbol。
如果使用 shopt 内置命令启用了 extglob shell 选项,shell 将识别几个扩展的模式匹配操作符。在下面的描述中,pattern-list 是由一个或多个模式组成的列表,这些模式由 | 分隔。当匹配文件名时,dotglob shell 选项决定了要测试的文件名集合,如上所述。复合模式可以使用以下一个或多个子模式形成:
?(pattern-list):匹配零次或一次给定的匹配模式;*(pattern-list):匹配零次或多次给定的匹配模式;+(pattern-list):匹配一次或多次给定的匹配模式;@(pattern-list):匹配一次给定的匹配模式;!(pattern-list):匹配除给定模式之一以外的任何内容。
extglob 选项改变了解析器的行为,因为通常括号被视为具有语法意义的操作符。为了确保扩展匹配模式能够被正确解析,请在解析包含这些模式的结构之前(包括 shell 函数和命令替换)确保 extglob 已启用。
当匹配文件名时,dotglob shell 选项决定了要测试的文件名集合:当 dotglob 启用时,文件名集合包括所有以 . 开头的文件,但文件名 . 和 .. 必须由以点号开头的模式或子模式来匹配;当它被禁用时,除非模式或子模式以 . 开头,否则文件名集合不包括任何以 . 开头的文件名。如上所述,. 仅在匹配文件名时具有特殊含义。
针对长字符串的复杂扩展模式匹配是缓慢的,特别是当模式中包含选择项且字符串中包含多个匹配时。对较短的字符串进行单独匹配,或者使用字符串数组而不是单个长字符串,可能会更快。
3.5.9 括号移除(Quote Removal)
在上述展开之后,所有未被引用的字符 \、' 和 "(这些字符不是由上述展开之一产生的)将被移除。
















