九、展开
命令行的展开是在拆分成词之后进行的。有七种类型的展开:花括号展开,波浪线展开,参数和变量展开,命令替换,算术展开,分行成词,路径展开。
展开的顺序是:花括号展开,波浪线展开,参数和变量展开,命令替换,算术展开,分行成词,路径展开,按照从左到右的顺序展开。
在系统上还有一种可用的额外的展开:进程替换。
只有花括号展开,分行成词和路径展开在展开前后的词数会发生改变;其他展开总是将一个词展开为一个词。 唯一的例外是上面提到的"$@"和"${name[@]}"。
花括号展开
花括号展开是一种可能产生任意字符串的机制。这种机制类似于路径展开,但是并不需要存在相应的文件。 花括号展开的模式是一个可选的前导字符,后面在一对花括号中跟着一系列逗号分隔的字符串或一组表达式,再后面是一个可选的附言。前导被添加到花括号中的每个字符串前面,附言被附加到每个结果字符串之后,从左到右进行展开。
花括号展开可以嵌套。展开字符串的结果没有排序;而是保留了从左到右的顺序。 例如:a{d,c,b}e 展开为"ade ace abe"。
可以使用{x..y[..incr]}的形式来表示一个有序的整数序列。意味着是从x开始到y结束,以"incr"为递增量的等差数列。x可以是0,"incr"是可选的,如果没有指定,默认为"1",即以1为增量递增。如果x和y相同,则只表示一个数字;如果"incr"为0,则该数值无效,仍然以默认值为"1"进行数字递增。如{1..10}表示从1到10的十个整数;{1..10..2}表示从1到10的所有的奇数;{2..10..2}表示从2到10所有的偶数等。当然也可以让x比y大,这样就可以得到从大到小倒序排列的一组数字,如:{100..1}就是从100开始倒数到1的所有整数。不管是x还是y,只要表示的是小数,都可以为"0"。而默认的"incr"为"1"或"-1"。
花括号展开是在任何其他展开之前进行的,任何对其他展开有特殊意义的字符 都保留在结果中。它是严格按字面完成展开的。Bash不会对展开的上下文或花括号中的文本做任何语义上的解释。
正确的花括号展开必须包含没有被引用的左花括号和右花括号,以及至少一个没有被引用的逗号。任何不正确的表达式都不会展开,保持原样不变。可以用反斜杠来引用"{"或","。来阻止将它们识别为花括号表达式的一部分。为了避免与参数展开发生冲突,对于花括号展开来讲,字符串"${"不被认为有效的组合。
当字符串生成的普通前缀远比上例中为长的时候,通常用这种结构来简写。例如:
mkdir /usr/local/src/bash/{old,new,dist,bugs}
或者:
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}
花括号展开导致了与历史版本的sh的一点不兼容。在左括号或右括号作为词的一部分出现时,sh不会对它们进行特殊处理,会在输出中保留它们。Bash将括号从花括号展开结果的词中删除。例如,向sh输入file{1,2}会导致不变的输出。同样的输入在 bash 进行展开之后,会输出file1和file2。如果需要同sh严格地保持兼容,需要在启动 bash的时候使用+B选项,或者使用set命令加上+B选项来禁用花括号展开。
波浪线展开
如果一个词以没有引用的波浪线字符(~)开始,所有在第一个没有引用的斜线(/)之前的字符(或者是这个词的所有字符,如果没有没引用的斜线的话)都被认为是波浪线前缀。如果波浪线前缀中没有被引用的字符,那么波浪线之后的字符串被认为是登录名。如果登录名是空字符串,波浪线将被替换为shell参数HOME的值。如果没有定义HOME,正在执行shell的用户的家目录会被替换。否则波浪线前缀就会用与指定的登录名关联的主目录来替换。
如果波浪线前缀是"~+",将使用shell变量PWD的值来替换。如果波浪线前缀是"~-",并且设置了shell变量OLDPWD,将使用这个变量值来替换。如果在波浪线前缀中,波浪线之后的字符串由一个数字N组成,前缀可选的"+"或者"-",那么波浪线前缀将被替换为目录栈中相应的元素,就是将波浪线前缀作为参数执行内置命令dirs显示的结果。如果波浪线前缀中波浪线之后的字符是一个数字,没有前缀,那么就假定有一个"+"。
如果登录名不合法,或者波浪线展开失败,这个词将不会变化。
在变量赋值中,对于":"或"="之后的字符串会立即使用未被引用的波浪线前缀检查。这种情况下,仍然会进行波浪线展开。因此,可以使用带波浪线的文件名来为PATH, MAILPATH和CDPATH赋值,shell将赋予展开之后的值。
参数展开
字符"$"引入了参数展开,命令替换和算术展开。要展开的参数名称或符号可用大括号括起来,这是可选的,但是可以使得要展开的变量不会与紧随其后的字符合并,成为变量名的一部分。
使用花括号的时候,匹配的结束的右括号必须是第一个并且是没有被反斜杠或引号引用的'}',也没有包含在一个嵌入算术展开,命令替换或是参数展开中。
${parameter}
被替换为parameter的值。如果parameter是一个位置参数,并且数字多于一位时;或者当紧随parameter之后有不属于名称一部分的字符时,都必须加上花括号。
如果parameter的第一个字符是一个感叹号,将引进一层间接变量。bash使用以parameter的其余部分为名的变量的值作为变量的名称;接下来新的变量被展开,它的值用在随后的替换当中,而不是使用parameter自身的值。这也称为间接展开。${!prefix*}和${!name[@]}是一种例外情况。
下面的每种情况中,word都要经过波浪线展开,参数展开,命令替换和算术展开。如果不进行子字符串展开,bash测试一个没有定义或值为空的参数;忽略冒号的结果是只测试未定义的参数。
${parameter:-word}
使用默认值。如果parameter未定义或值为空,将替换为word的展开。否则,将替换为parameter的值。
${parameter:=word}
赋默认值。如果parameter未定义或值为空,word的展开将赋予parameter。parameter的值将被替换。位置参数和特殊参数不能用这种方式赋值。
${parameter:?word}
显示错误,是否未定义或值为空。如果parameter未定义或值为空,word的展开将被写入到标准错误;如果shell不是交互的,则退出。否则,parameter的值将被替换。
${parameter:+word}
使用可选值。如果parameter未定义或非空,不会进行替换;否则将替换为word展开后的值。
${parameter:offset}
${parameter:offset:length}
子字符串展开。展开为parameter的最多length个字符,从offset指定的字符开始。如果忽略了length,展开为parameter的子字符串,从offset指定的字符串开始。length和offset是算术表达式。length必须是一个大于等于0的数值。如果offset求值结果小于0,值将当作从parameter的值的末尾算起的偏移量。如果parameter是@,结果是length个位置参数,从offset开始。如果parameter是一个数组名,以"@"或"*"索引,结果是数组的length个成员,从${parameter[offset]}开始。子字符串的下标是从0开始的,除非使用位置参数时,下标从1开始。
${!prefix*}
${!prefix@}
名称匹配前缀。展开为名称以prefix开始的变量名,以特殊变量IFS的第一个字符分隔。当"@"被使用并且在双引号中被展开的时候,每一个变量名被展开为一个单独的词。
${!name[@]}
${!name[*]}
列表显示数组的下标。如果name是一个数组变量,展开为以name中被赋值的元素的下标号。如果name不是数组,展开为0。如果当"@"被使用并且在双引号中被展开的时候,每一个变量名被展开为一个单独的词。
${#parameter}
参数长度。替换为parameter的值的长度(字符数目)。如果parameter是*或者@, 替换的值是位置参数的个数。如果 parameter是一个数组名,下标是*或者是@,替换的值是数组中元素的个数。
${parameter#word}
${parameter##word}
删除匹配前缀模式。word被展开为一个模式,就像路径展开中一样。如果这个模式匹配parameter的值的起始,那么展开的结果是将parameter展开后的值中,最短的匹配(#的情况)。或者最长的匹配(``##''的情况)删除的结果。如果 parameter是@或者是*,则模式删除操作将依次施用于每个位置参数,最后展开为结果的列表。如果parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后展开为结果的列表。
${parameter%word}
${parameter%%word}
移除匹配后缀模式。word被展开为一个模式,就像路径展开中一样。如果这个模式匹配parameter展开后的值的尾部,那么展开的结果是将parameter展开后的值中,最短的匹配(%的情况)或者最长的匹配(%%的情况)删除的结果。如果 parameter是@或者是*,则模式删除操作将依次施用于每个位置参数,最后展开为结果的列表。如果parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后展开为结果的列表。
${parameter/pattern/string}
${/parameter/pattern/string}
模式替换。pattern被展开为一个模式,就像路径展开一样。parameter被展开,其值中最长的匹配pattern的内容被替换为string。如果模式是以"/"开头的,所有匹配的都被string替换。正常情况下值有第一个匹配的被string替换。如果pattern以#开始,它必须匹配parameter展开后值的首部。如果pattern以%开始,它必须匹配parameter展开后值的尾部。如果string是空值,pattern的匹配都将被删除,pattern之后的/将被忽略。如果parameter是@或者是*,则替换操作将依次施用于每个位置参数,最后展开为结果的列表。如果parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后展开为结果的列表。
${parameter^pattern}
${parameter^^pattern}
${parameter,pattern}
${parameter,,pattern}
大小写修改。这个展开修改了patameter中字母字符的大小写。pattern被展开为一个模式,就像路径展开一样。"^"操作符匹配小写字母转换成大写字母;","操作符匹配大写字母转换成小写字母;"^^"和",,"展开转换每一个在展开值中匹配的字符。"^"和","只转换展开值中的第一个字符。如果模式被略过,就像?那样处理,这将匹配每一个字符。如果parameter是"@"或"*",大小写改写操作就会按顺序在每个位置参数上执行,并且展开所得结果的列表。如果parameter是一个数组变量下标是"@"或"*",大小写修改操作会按顺序轮流在每个数组成员上进行,并且展开最终结果列表。
命令替换
命令替换允许以命令的输出替换命令名。有两种形式:
$(command)
`command`
Bash通过执行命令并且以它的标准输出替换命令本身的方式实现展开,并且将所有后续的换行符删除。内嵌的换行符不会删除,但是它们可能会在词的拆分中被删除。命令替换$(cat file)可以用等价但是更快的方法$(< file)代替。
当使用旧式的反引号"``"替换形式时,反斜杠只有其字面意义,除非后面是$,`,或者是\.。第一个前面没有反斜杠的反引号将结束命令替换。当使用$(command)形式时,括号中所有字符组成了整个命令;没有字符被特殊处理。
命令替换可以嵌套。要在使用反引号形式时嵌套,可以用反斜杠来转义内层的反引号。
如果替换发生在双引号之中,结果是将不进行词的拆分和路径展开。
算术展开
算术展开允许算术表达式的求值和结果的替换。算术展开的格式是:
$((expression))
表达式expression被视为如同在双引号之中一样,但是括号中的双引号不会被特殊处理。表达式中所有词都经过了参数展开,字符串展开,命令替换和引用的删除。算术替换可以嵌套。
bash将根据规则进行计算,如果表达式无效,则会显示一条消息来指示故障并不做替换操作
进程替换
进程替换只有在支持命名管道(FIFOs)或者支持使用命名方法/dev/fd打开文件的时候,在系统中才可用。它的形式是<(list)或者是>(list)。进程list运行时的输入或输出被连接到一个FIFO或者/dev/fd中的文件。文件名以展开结果的身份作为一个参数被传递给当前命令。如果使用>(list)形式,写入到文件中就是为list提供输入。如果使用<(list)形式,文件作为应该被读取的参数传递以获得list的输出。
如果可能的话,在参数和变量展开、命令替换和算术展开的同时执行进程替换。
字拆分
shell为了进行字拆分检测那些没有在双引号引用中发生的参数展开,命令替换和算术展开的结果。
shell将IFS的每个字符都作为定界符,根据这些字符来将其他展开的结果分成词。如果IFS没有定义,或者它的值就是"<space>""<tab>""<newline>",那么默认情况下,在之前展开的结果中的开始或结束位置出现的一系列的"<space>""<tab>""<newline>"都将被忽略,任何不在开始和结束位置的IFS字符的序列都将用于词定界。如果IFS的值是默认之外的值,那么词开头和结尾的空白字符"<space>"和"<tab>"都将被忽略,只要空白字符在IFS的值之内(即,IFS包含空白字符)。 任何在IFS之中但不是IFS空白字符的字符,以及任何相邻的IFS空白字符,将分隔字段。 一系列的IFS空白字符也被当作定界符。如果IFS的值是空,不会做字拆分处理。
显式给出的空值参数("" 或 '')将被留存。未被引用的自于无值的参数展开隐式空值参数,将被删除。如果无值的参数在双引号引用中展开,空值的参数作为结果将被保留。
注意如果没有执行展开,就不会进行字拆分。
路径名展开
字拆分之后,除非设置过-f选项,否则bash搜索每个词,寻找字符"*","?"和"["。如果其中任意一个出现,那么这个字被当作一个模式,并按字母顺序替换匹配模式的的文件名的列表。如果没有发现可以匹配的文件名,并且shell禁用了nullglob选项,这个词将不发生变化。如果设置了nullglob选项但是没有发现匹配项,这个字将被删除。如果设置了failglob选项但是没有发现匹配项,会显示一条错误信息并且不会执行该命令。如果shell启用了nocaseglob选项,匹配时将不考虑字母的大小写。当模式用于路径名展开时,字符"."在一个名称的起始处或者马上跟上一个斜杠,那么它必须被显式地匹配,除非设置了shell的dotglob选项。当匹配一个路径名时,斜杠符必须总是被显式地匹配。其他情况下,字符"."不会做特殊处理。
shell的环境变量GLOBIGNORE可以用来严格限制一系列被模式匹配的文件名。如果设置了GLOBIGNORE,每一个匹配的文件名也匹配了GLOBIGNORE中任何一个模式的话,就会从匹配列表中将之删除。当GLOBIGNORE被定义并且非空的时候,文件名"."和".."总是被忽略。然而,将环境变量GLOBIGNORE设置为非空值有开启dotglob这个shell选项的效果,因此所有其他以"."开始的文件名会被匹配。为了获得忽略以"."开头的文件名的行为,可以将".*"添加为GLOBIGNORE的模式之一。在GLOBIGNORE没有定义时dotglob选项将被禁用。
模式匹配
任何出现在模式中的字符,除了下面描述的特殊模式字符外,都只匹配它本身。模式中不能出现"NUL"字符。反斜线用于转义下列字符;用于逃逸的反斜线在匹配时被丢弃。如果要匹配字面上的特殊模式字符,它必须被引号引用。
特殊模式字符有下述意义:
*
匹配任何字符串包含空串。当shell的globstar选项被打开,"*"用于路径名展开上下文,两个相邻的"*s"作为一个匹配所有文件、0个或多个目录和子目录的单一的模式。如果其后跟随一个"/",两个相邻的"*s"只能匹配目录和子目录。
? 匹配任何单个字符。
[...]
匹配被一对"[]"封闭的任意单个字符。一对用连字符(-)分隔的字符代表一个范围表达式;任何排在它们之间的字符,包含使用当前语言环境的排序顺序和字符集字符,都被匹配。如果"["之后的第一个字符是一个"!"或是一个"^",那么任何不包含在内的字符将被匹配。范围表达式中字符的顺序是由当前语言环境和环境变量LC_COLLATE的值(如果设置了的话)决定的。"-"只有作为集合中第一个或最后一个字符时才能被匹配。一个"]"只有是集合中第一个字符时才能被匹配。
在"["和"]"中,字符类可以用[:class:]这样的语法来指定,这里class是在POSIX.2标准中定义的下列类名之一:
alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit
一个字符类匹配任何属于这一类的字符。关键字字符类匹配字母,数字和"_"字符。
在"["和"]"中,可以用[=c=]这样的语法来指定等价类。它匹配与字符"c"有相同归并权值(由当前语言环境定义)的字符。
在"["和"]"中,语法[.symbol.]匹配归并符号symbol。
几种扩展的模式匹配操作符需要认识。下面的描述中,"pattern-list"是一个或多个模式以"|"分隔的列表。复合模式可以使用下列一个或多个子模式构造出来:
?(pattern-list)
匹配所给模式零次或一次出现
*(pattern-list)
匹配所给模式零次或多次出现
+(pattern-list)
匹配所给模式一次或多次出现
@(pattern-list)
准确匹配所给模式之一
如果内置命令shopt的extglob选项开启,下列模式匹配操作符也是可是被识别的:
!(pattern-list)
任何除了匹配所给模式之一的字串
引用去除
经过前面的展开之后,所有未引用的字符\,',以及并非上述展开结果中的"字符都被删除。
十、重定向
在命令执行前,它的输入和输出可能通过shell使用一种特殊标记法来解释而被重定向。重定向也可以用于为当前shell执行环境打开和关闭文件。下列重定向操作符可以前置或者放在简单命令之中的任何位置,或者放在命令之后。重定向是以出现的顺序进行处理的,从左到右。
每一个可能在文件描述符数字之前的重定向会替代以{varnaem}形式为前导的字。在这种情况下,shell会为除了">&-"和"<&-"之外的每一个重定向操作符,分配一个大于等于10的文件描述符,并且为varname赋值。如果">&-"和"<&-"在{varname}前面,varname定义的文件描述符的值到此结束。
下列描述中,如果文件描述符被略过,并且第一个重定向操作符是"<", 那么重定向指的是标准输入(文件描述符是0)。如果重定向操作符的第一个字符是>, 那么重定向指的是标准输出(文件描述符是1)。
下列描述中,重定向操作符之后的词如果没有特殊说明,都要经过花括号展开,波浪线展开,参数展开,命令替换,算术展开,移除引用,路径展开还有词划分。如果展开为多于一个词,bash将报错。
注意重定向的顺序非常重要。例如,命令
ls > dirlist 2>&1
将标准输出和标准错误定向到文件dirlist, 而命令
ls 2>&1 > dirlist
只会将标准输出定向到文件dirlist,因为在标准输出被重定向到文件dirlist中之前,标准错误被复制到标准输出。
一些文件名在重定向中被bash特殊处理,如下表所示:
/dev/fd/fd
如果fd是一个合法的整数,文件描述符fd将被复制。
/dev/stdin
文件描述符0被复制。
/dev/stdout
文件描述符1被复制。
/dev/stderr
文件描述符2被复制
/dev/tcp/host/port
如果host是一个合法的主机名或IP地址,并且port是一个整数端口号或服务名,bash试图建立与相应的套接字的TCP连接。
/dev/udp/host/port
如果host是一个合法的主机名或IP地址,并且port是一个整数端口号或服务名,bash试图建立与相应的套接字的UDP连接。
打开或创建文件失败将导致重定向失败。
使用文件描述符大于9的重定向应谨慎,因为它们可能和shell内部使用的文件描述符有冲突。
重定向输入
重定向输入使得以"word"展开结果为名的文件被打开并通过文件描述符n读取,如果没有指定n,那么就作为标准输入(文件描述符为0)读取。
重定向输入的一般形式是:
[n]<word
重定向输出
重定向输出使得以"word"展开结果为名的文件被打开并通过文件描述符n写入,如果没有指定n,那么
就作为标准输出(文件描述符为 )写入。如果文件不存在就创建之;如果文件确实存在就截断到0字节。
重定向输出的一般形式是:
[n]>word
如果重定向操作符是">",并且启用了内置命令set的noclobber选项,如果word展开后得到的文件名存在并且是一个普通的文件,重定向将失败。如果重定向操作符是">|",或者重定向操作符是">"并且没有启用内置命令set的noclobber选项,那么即使word得出的文件名存在,也会尝试进行重定向。
追加重定向输出
这种方式的输出重定向使得以word展开结果为名的文件被打开并通过文件描述符n从尾部添加。如果没有指定n就使用标准输出(文件描述符1)。如果文件不存在,就创建之。
重定向的一般形式是:
[n]>>word
重定向标准输出和标准错误
Bash允许使用这种结构将标准输出和标准错误(文件描述符1和2)重定向到以word展开结果为名的文件中。
有两种重定向标准输出/标准错误的形式:
&>word
>&word
两种形式中,推荐使用第一种。它与下面的使用方法在语义上等价:
>word 2>&1
追加重定向标准输出和标准错误
这种结构允许将标准输出(文件描述符1)和标准错误(文件描述符2)附加到以word展开结果为名的文件的最后。
附加标准输出和标准错误的格式为:
&>>word
它与下面的使用方法在语义上等价:
>>word 2>&1
此处文档
这种重定向结构使得shell从当前源文件读取输入,直到遇到仅包含word的一行(并且没有尾部空白)为止。直到这一点的所有行被用作命令的标准输入。
此处文档的格式是:
<<[-]word
here-document
delimiter
不会对word进行参数展开,命令替换,算术展开或者路径展开。如果word中任何字符是引用的,delimiter将是对 word进行引用删除的结果,此处文档中的行不会被展开。如果word没有被引用,此处文档中的所有行都要经过参数展开,命令替换和算术展开。 在后一种情况下,字符序列"\<newline>"被忽略;必须用"\"来引用字符"\","$",和"`"。
如果重定向操作符是"<<-",那么所有前导的tab字符都被从输入行和包含delimiter的行中删除。这样使得shell脚本中的此处文档可以被更好地缩进。
此处字符串
此处文档的变种,形式是
<<<word
word被展开,提供给命令作为标准输入。
复制文件描述符
[n]<&word
重定向操作符用于复制文件描述符。如果word展开为一个或多个数字,n代表的文件描述符将成为那个文件描述符的复制。如果word中的数字并未指定一个被用于读取的文件描述符,将产生一个重定向错误。如果word展开为"-",文件描述符n将被关闭。如果没有指定n,将使用标准输入(文件描述符0)。
[n]>&word
此操作符同样用于复制输出文件描述符。如果没有指定n,将使用标准输出(文件描述符1)。如果word中的数字并未指定一个被用于输出的文件描述符,将产生一个重定向错误。特殊情况下,如果n被略过,并且word并非展开为一个或多个数字,标准输出和标准错误将被重定向,和前面描述的一样。
移动文件描述符
重定向操作符
[n]<&digit-
将文件描述符digit移动为文件描述符n,或标准输入(文件描述符0),如果没有指定n的话。 digit复制为n之后就被关闭了。
类似的,重定向操作符
[n]>&digit-
将文件描述符digit移动为文件描述符n,或标准输出(文件描述符1),如果没有指定n的话。
为读写操作打开文件描述符
重定向操作符
[n]<>word
使得以word展开结果为名的文件被打开,通过文件描述符n进行读写。如果没有指定n那么就使用文件描述符0。如果文件不存在,它将被创建。