3.4. Shell 展开

3.4.1. 概述

在命令被分割成令牌 (tokens) 后 (参见 第 1.4.1.1 节),这些令牌或词语会被展开或解析。总共有八种展开方式,我们将在接下来的章节中按照展开顺序讨论它们。

在所有展开完成后,会执行引号移除操作。

3.4.2. 花括号展开

花括号展开是一种可以生成任意字符串的机制。花括号展开的模式形式为:一个可选的前缀 (PREAMBLE),后跟一对花括号之间的用逗号分隔的字符串序列,最后是一个可选的后缀 (POSTSCRIPT)。前缀会添加到花括号中包含的每个字符串之前,然后后缀会添加到每个结果字符串之后,从左到右展开。

花括号展开可以嵌套。每个展开字符串的结果不会排序;会保留从左到右的顺序。

franky ~> echo sp{el,il,al}l
spell spill spall

花括号展开在任何其他展开之前执行,并且任何对于其他展开具有特殊意义的字符都会在结果中保留。它是严格的文本操作。 Bash 不会对展开的上下文或花括号之间的文本应用任何语法解释。为了避免与参数展开冲突,字符串 "${" 不被认为适合进行花括号展开。

一个格式正确的花括号展开必须包含未被引号包裹的开始和结束花括号,以及至少一个未被引号包裹的逗号。任何格式不正确的花括号展开都将保持不变。

3.4.3. 波浪号展开

如果一个词语以一个未被引号包裹的波浪号字符 ("~") 开始,则直到第一个未被引号包裹的斜杠(如果没有未被引号包裹的斜杠,则是所有字符)的所有字符都被认为是波浪号前缀。如果波浪号前缀中没有字符被引号包裹,则波浪号后面的字符将被视为可能的登录名。如果此登录名是空字符串,则波浪号会被替换为HOMEshell 变量的值。如果HOME未设置,则会替换为执行 shell 的用户的家目录。否则,波浪号前缀会被替换为与指定登录名关联的家目录。

如果波浪号前缀是 "~+",则 shell 变量PWD的值会替换波浪号前缀。如果波浪号前缀是 "~-",则 shell 变量OLDPWD的值(如果已设置)会被替换。

如果波浪号前缀中波浪号后面的字符由一个数字 N 组成,可以选择在前面加上一个 "+""-",则波浪号前缀会被替换为目录堆栈中的相应元素,就像使用波浪号前缀后面的字符作为参数调用内置命令 dirs 所显示的那样。如果波浪号前缀(不带波浪号)由一个没有前导 "+""-" 的数字组成,则假定为 "+"

如果登录名无效,或者波浪号展开失败,则该词语将保持不变。

每个变量赋值都会检查紧跟在 ":""=" 之后的未被引号包裹的波浪号前缀。在这些情况下,也会执行波浪号展开。因此,可以在赋值中对PATH, MAILPATHCDPATH使用带有波浪号的文件名,并且 shell 会分配展开后的值。

示例

franky ~> export PATH="$PATH:~/testdir"

~/testdir将被展开为$HOME/testdir, 所以如果$HOME/var/home/franky, 那么目录/var/home/franky/testdir将被添加到PATH变量的内容中。

3.4.4. Shell 参数和变量展开

"$" 字符引入参数展开、命令替换或算术展开。要展开的参数名称或符号可以用花括号括起来,这些花括号是可选的,但可以保护要展开的变量,使其免受紧随其后的可能被解释为名称一部分的字符的影响。

当使用花括号时,匹配的结束花括号是第一个未被反斜杠转义、不在带引号的字符串内、也不在嵌入式算术展开、命令替换或参数展开内的 "}"

参数展开的基本形式是 "${PARAMETER}""PARAMETER" 的值会被替换。当 "PARAMETER" 是一个具有多个数字的位置参数,或者当 "PARAMETER" 后跟一个不被解释为其名称一部分的字符时,需要使用花括号。

如果 "PARAMETER" 的第一个字符是感叹号,则 Bash 会使用从 "PARAMETER" 的其余部分形成的变量的值作为变量的名称;然后展开这个变量,并将该值用于替换的其余部分,而不是 "PARAMETER" 本身的值。这被称为间接展开

你肯定熟悉直接参数展开,因为它一直发生,即使是最简单的情况下,例如上面的情况或以下情况

franky ~> echo $SHELL
/bin/bash

以下是间接展开的示例

franky ~> echo ${!N*}
NNTPPORT NNTPSERVER NPX_PLUGIN_PATH

请注意,这与 echo $N* 不同。

以下构造允许创建命名变量(如果它还不存在)

${VAR:=}

示例

franky ~> echo $FRANKY

franky ~> echo ${FRANKY:=Franky}
Franky

但是,特殊参数(包括位置参数)不能以这种方式分配。

我们将在 第 10 章 中进一步讨论使用花括号来处理变量。 更多信息也可以在 Bash info 页面中找到。

3.4.5. 命令替换

命令替换允许用命令的输出替换命令本身。 当命令像这样被括起来时,会发生命令替换

$(command)

或者像这样使用反引号

`command`

Bash 通过执行 COMMAND 并用命令的标准输出替换命令替换来执行展开,并删除任何尾随换行符。 嵌入的换行符不会被删除,但可能会在单词分割期间被删除。

franky ~> echo `date`
Thu Feb 6 10:06:20 CET 2003

当使用旧样式的反引号形式的替换时,反斜杠保留其字面意义,除非后面跟着 "$""`""\"。 第一个未被反斜杠转义的反引号结束命令替换。 当使用 "$(COMMAND)" 形式时,括号之间的所有字符组成命令; 没有字符被特殊处理。

命令替换可以嵌套。 要在使用反引号形式时进行嵌套,请使用反斜杠转义内部反引号。

如果替换出现在双引号内,则不会对结果执行单词分割和文件名展开。

3.4.6. 算术展开

算术展开允许计算算术表达式并替换结果。 算术展开的格式为

$(( EXPRESSION ))

该表达式被视为位于双引号内,但括号内的双引号不会被特殊处理。 表达式中的所有令牌都会经历参数展开、命令替换和引号删除。 算术替换可以嵌套。

算术表达式的求值是以固定宽度的整数完成的,不检查溢出 - 尽管除以零会被捕获并识别为错误。 运算符与 C 编程语言中的运算符大致相同。 按照降序排列,列表如下所示

表 3-4. 算术运算符

运算符含义
VAR++ 和 VAR--变量后增和后减
++VAR 和 --VAR变量前增和前减
- 和 +一元负号和正号
! 和 ~逻辑和按位取反
**求幂
*, / 和 %乘法、除法、余数
+ 和 -加法、减法
<< 和 >>左移和右移
<=, >=, < 和 >比较运算符
== 和 !=相等和不等
&按位与
^按位异或
|按位或
&&逻辑与
||逻辑或
expr ? expr : expr条件求值
=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^= 和 |=赋值
,表达式之间的分隔符

Shell 变量允许作为操作数; 在计算表达式之前执行参数展开。 在表达式中,也可以通过名称引用 shell 变量,而无需使用参数展开语法。 当引用时,变量的值被评估为算术表达式。 Shell 变量不需要打开其整数属性才能在表达式中使用。

以 0(零)开头的常量被解释为八进制数。 以 "0x""0X" 开头表示十六进制。 否则,数字采用 "[BASE'#']N" 形式,其中 "BASE" 是一个介于 2 和 64 之间的十进制数,表示算术基数,N 是该基数中的一个数字。 如果省略 "BASE'#'",则使用 10 作为基数。 大于 9 的数字由小写字母、大写字母、"@""_" 依次表示。 如果 "BASE" 小于或等于 36,则小写字母和大写字母可以互换使用来表示 10 到 35 之间的数字。

运算符按照优先级顺序进行评估。 括号中的子表达式首先被评估,并且可能会覆盖上面的优先级规则。

Bash 用户应尽可能尝试使用带有方括号的语法

$[ EXPRESSION ]

然而,这只会计算EXPRESSION的结果,不做任何测试。

franky ~> echo $[365*24]
8760

有关脚本中的实际示例,请参见第 7.1.2.2 节等。

3.4.7. 进程替换

进程替换在支持命名管道(FIFO)或/dev/fd命名打开文件的方法的系统上受支持。它采用以下形式:

<(LIST)

>(LIST)

进程LIST运行,其输入或输出连接到 FIFO 或/dev/fd中的某个文件。此文件的名称作为扩展结果传递给当前命令作为参数。如果使用">(LIST)"形式,则写入文件将为LIST提供输入。如果使用"<(LIST)"形式,则应读取作为参数传递的文件以获取LIST的输出。请注意, < 或 > 符号与左括号之间不能有空格,否则该结构将被解释为重定向。

如果可用,进程替换将与参数和变量扩展、命令替换和算术扩展同时执行。

更多信息请参见第 8.2.3 节

3.4.8. 单词分割

shell 扫描未在双引号中发生的参数扩展、命令替换和算术扩展的结果,以进行单词分割。

shell 将$IFS的每个字符视为分隔符,并将其他扩展的结果按照这些字符分割成单词。如果IFS未设置,或者其值恰好是"'<space><tab><newline>'",即默认值,则任何IFS字符序列都用于分隔单词。如果IFS的值不是默认值,则空白字符"space""Tab"的序列将在单词的开头和结尾被忽略,只要空白字符在IFS的值中(一个IFS空白字符)。IFS中不是IFS空白的任何字符以及任何相邻的IFS空白字符都会分隔一个字段。IFS空白字符序列也被视为分隔符。如果IFS的值为空,则不会发生单词分割。

显式空参数("""""''")会被保留。由没有值的参数的扩展产生的未加引号的隐式空参数将被删除。如果在双引号内扩展没有值的参数,则会产生一个空参数并被保留。

Note扩展和单词分割
 

如果没有发生扩展,则不执行分割。

3.4.9. 文件名扩展

在单词分割之后,除非设置了-f选项(请参见第 2.3.2 节),否则 Bash 会扫描每个单词中的字符"*""?""["。如果其中一个字符出现,则该单词被视为 *PATTERN*,并替换为与该模式匹配的按字母顺序排列的文件名列表。如果没有找到匹配的文件名,并且 shell 选项nullglob被禁用,则该单词保持不变。如果设置了nullglob选项,并且没有找到匹配项,则该单词将被删除。如果 shell 选项nocaseglob被启用,则执行匹配时不考虑字母字符的大小写。

当模式用于文件名生成时,文件名开头或紧跟在斜杠后的字符 "." 必须显式匹配,除非 shell 选项dotglob被设置。当匹配文件名时,斜杠字符必须始终显式匹配。在其他情况下,字符 "." 不被特殊处理。

shell 变量GLOBIGNORE可用于限制与模式匹配的文件名集。如果GLOBIGNORE被设置,则与GLOBIGNORE中的模式之一匹配的每个匹配文件名都会从匹配列表中删除。文件名...始终被忽略,即使GLOBIGNORE被设置。但是,设置GLOBIGNORE具有启用dotglobshell 选项的效果,因此所有其他以 "." 开头的文件名都将匹配。要获得忽略以 "." 开头的文件名的旧行为,请将 ".*" 设置为GLOBIGNORE中的模式之一。dotglob选项在GLOBIGNORE未设置时被禁用。