3.shell 基本特性之~ shell展开详解
第 3 章目录:
3.1 shell 语法
3.2 shell 命令
3.3 shell 函数
3.4 shell 参数
3.5 shell 展开
3.6 重定向
3.7 命令执行
3.8 shell 脚本
本文件内容为 "3.5 shell 展开"
3.5 小节目录:
3.5.1 花括号展开 对花括号中的表达式的展开
3.5.2 波浪线展开
3.5.3 shell 参数展开
3.5.4 命令替换 把命令的输出当做参数使用
3.5.5 算术展开 如何在 shell 表达式中进行算术运算
3.5.6 过程替换 一种对命令进行读写的方法
3.5.7 单词分割 展开结果是如何分割成独立的参数的
3.5.8 文件名展开
3.5.9 引用删除
命令行被分割为 token 之后,就开始进行展开。一共有 7 种展开:
1,花括号展开
2,波浪线展开
3,参数和变量展开
4,命令替换
5,算术展开
6,单词分割
7,文件名展开
展开的顺序是:
1,花括号展开
2,波浪线展开
3,参数,变量,算术展开和命令替换(在支持过程替换的系统上,过程替换也在这里进行)
4,单词分割
5,文件名展开
只有花括号展开,单词分割,文件名展开能改变展开结果的单词个数。
其他展开一般把一个单词展开为另一个单词,有两个例外情况:'"$@"' 和 '"${NAME[@]}"'。
所有展开完成后,进行引用删除
3.5.1 花括号展开
在花括号内,可以是以逗号分隔的字符串,或者是一个序列表达式。在花括号前后,可以跟前缀和后缀。
花括号展开支持嵌套,展开的字符串是无序的,从左到右的顺序被保留。
1,逗号分隔
# echo a{d,c,b}e
ade ace abe
前缀 a 和 后缀 e 与展开结果的每一个字符串结合生成最终结果。
2,序列表达式
序列表达式的语法是:
'{X..Y[..INCR]}'
X,Y 是数字或单个字符,INCR 是步进。
当 X,Y 是数字时,展开为 X 到 Y 的所有数字。数字可加前缀 0,如 01,001,使展开结果保持相同的宽度。
当 X 或 Y 以 0 开头时,shell 尝试将生成结果保持相同宽度。必要时以 0 进行填充。
{01..100} 生成 001, 002, ..., 100
步进:
{1..10..2} 生成 1 3 5 7 9
当 X,Y 是字母时,按字典序展开。X,Y 必须是相同类型。当给出步进时,相邻字母的距离为步进的距离。默认步进为 1 或 -1(看具体情况)。
{a..g..2} 生成 a c e g
花括号展开在其它展开之前进行,任何对于其它展开有特殊意义的字符都被保留。
在进行花括号展开时,bash 对于花括号的上下文和括号内的文本内容不做任何的语法解释。
为避免与参数展开产生冲突,字符串 '${' 不被认为需要做花括号展开。
要进行正确的花括号展开,必须包含未被引用(unquoted)的 '{' 和 '}'。并且在括号中,至少有一个未被引用的 ',' 或者一个有效的“序列表达式”。
书写不正确的花括号展开保留原样。
把 '{' 和 ',' 引用起来,可避免被认为是花括号展开的一部分。为避免与参数展开产生冲突,字符串 '${' 不被认为需要做花括号展开。
这个构造典型的应用场景是,当字符串前缀太长的时候,比如创建多个包含绝对路径的文件时,使用花括号展开可以简化命令行语句,如:
mkdir /usr/local/src/bash/{old,new,dist,bugs}
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}} # 两个子目录
3.5.2 波浪线展开
如果一个单词以未被引用的 '~' 开头,这个单词所有到斜线 '/'(如果有得话) 为止的字符被认为是一个 波浪线前缀字符串(TILDE-PREFIX)。
如果在 “波浪线前缀字符串” 中没有被引号引用的字符,那么所有波浪线之后的字符组成的字符串被当做可能的 LOGIN NAME 对待。
如果 LOGIN NAME 是空字符串,'~' 被替换为 'HOME' 变量的值。
如果 'HOME' 变量没有设置,则替换为执行 shell 的用户的家目录。
否则,'~' 被替换为与 登录名 关联的家目录。
$ echo $HOME #HOME 非空
/Users/guli
$ echo ~ #打印当前用户家目录
/Users/guli
$ echo ~guest #打印guest用户家目录
/Users/Guest
$ echo ~root #打印root用户家目录
/var/root
如果 波浪线前缀字符串 是 '~+',被替换为 'PWD' 变量的值。
如果是 '~-',被替换为 'OLDPWD' 变量的值(如果存在的话)。
如果 波浪线前缀字符串 是一个数字,数字前跟着一个 '+' 或 '-',如 '~+N',则被替换为 目录栈 的相应顺序的元素。等同于 dirs +/-N 的效果。
如果 '+' 或 '-' 没有给出,默认为 '+'。
如果 登录名 是无效的,或者波浪线展开失败,则保留原样。
$ echo ~guset #登录名 guset 是无效的
~guset
每个变量赋值会检查紧跟在 ':' 或第一个 '=' 后面是否有未被引号引用的 '~'。如果有,会进行波浪线展开。所以,我们可以通过这种方式赋值给 'PATH','MAILPATH','CDPATH',shell 会用展开后的结果赋值给变量。
下面是 bash 中的波浪线展开的示例:
'~'
展开为 '$HOME' 的值
'~/foo'
展开为 '$HOME/foo'
'~fred/foo'
展开为 用户fred的及目录的子目录foo: /home/fred/foo
'~+/foo'
展开为 '$PWD/foo'
'~-/foo'
展开为 '{OLDPWD-'~-'}/foo'
'~N'
等同于执行 'dirs +N' 的结果
'~+N'
等同于执行 'dirs +N' 的结果
'~-N'
等同于执行 'dirs -N' 的结果
3.5.3 Shell 参数展开
'$' 符号引入了三种 shell 展开,包括 “参数展开”,“命令替换” 和 “算术表达式”。
参数名或参数符号可以用花括号括起来,使紧跟在参数名后面的字符与之分隔,这些字符与展开的结果可共同构成最后字符串。如 ${PATH}:/path/to/..
在参数展开中使用花括号时,'{' 是开始符号,右边第一个 '}' 是结束符号。
'}' 不能被转义,或被引号引用,也不能在一个嵌套的 “算术表达式”,或 “命令替换”,或者 “参数展开”之中。
参数展开的基本的形式是 ${PARAMETER},整体被替换为 PARAMETER 的值。如果 PARAMETER 是位置参数,而且由两个及以上的数字表示,这时必须使用花括号:${10}。另外当 PARAMETER 与其它字符相邻连接时,也必须使用花括号:${Var}lala。
如果 PARAMETER 的第一个字符是 "!",会进行“间接变量展开”。bash 使用 "!" 之后的部分作为变量名,变量进行展开,变量展开结果作为被引用的对象再进行一次展开。"!" 符号必须紧跟在左括号 "{" 后面。但是 ${!PREFIX*} 和 ${!NAME[@]} 是例外,在下面会介绍。
在下面将要介绍的例子中,WORD 可进行 “波浪线展开”,“参数展开”,“命令替换” 以及 “算术展开”。也就是说,WORD 可以是 $VAR,$(CMD),$(EXPR),~ 形式的。
如果 WORD 是字符串,就不会做展开,在这里不是作为变量名使用的。
当不进行子字符串展开时,使用下面的形式,bash 会测试 PARAMETER
是否是 unset,或是否是空字符串(null)。
如果删除冒号 ':',只会测试 PARAMETER 是否是 unset(是否存在)。
'${PARAMETER:-WORD}'
如果 PARAMETER 是 unset 或者 null,最终展开结果为 WORD 的展开(普通字符串的展开结果是原字符串保持不变,这里不是取 WORD 的值为最终结果)。否则,使用 PARAMETER 的变量值为最终结果。
'${PARAMETER:=WORD}'
如果 PARAMETER 是 unset 或者 null,将 WORD 的展开结果赋值给PARAMETER(不是 WORD 的参数值),最终结果为 PARAMETER 的值。但是“位置参数”和“特殊参数”不可通过这种方式赋值。
'${PARAMETER:?WORD}'
如果 PARAMETER 是 unset 或者 null,将 WORD 的展开结果(如果未给出 WORD,会有一条消息)写入标准错误输出以及 shell,如果 shell 是非交互式的,就退出 shell。否则,使用 PARAMETER 的变量值最终结果。
'${PARAMETER:+WORD}'
如果 PARAMETER 是 unset 或者 null,结果是 nothing。否则 WORD 的展开结果为最终结果。
'${PARAMETER:OFFSET}'
'${PARAMETER:OFFSET:LENGTH}'
1,当 PARAMETER 是普通变量时,以字符为单位对参数值做截取。
OFFSET: 从0开始的位移,截取从位移处开始。
LENGTH: 截取的字符个数。如果没有写明 LENGTH,即截取从位移处到最后一个
字符。
OFFSET 和 LENGTH 是“算数表达式”。这种展开可被称为“子字符串展开”。
LENGTH 必须大于等于1,当OFFSET是负数时,表示从参数值的尾部向前截取。
2,当 PARAMETER 是位置参数时,比如'@',表示从OFFSET处开始的LENGTH个位置参数。
3,当 PARAMETER 是以@或*为下标的数组时,表示从'${PARAMETER[OFFSET]}'开始的 LENGTH 个数组变量。
OFFSET 如果是负数,它的计数从数组最大下标+1的地方计算。
-1 是最后一个元素的索引
“子字符串展开”用到数组上时会产生未知的结果。
注意:负数的 offset 必须用空格与冒号':'隔开,以免和“:-展开”混淆。
对于字符串,索引从 0 开始。(0-based)
对于位置参数,索引从 1 开始。(1-based)
对于数组,索引从 0 开始。(0-based)
'${!PREFIX*}'
'${!PREFIX@}'
展开结果为所有以 PREFIX 为前缀的变量的名字。这些变量名以"IFS"变量的第
一个字符作为分隔符。
如果用的是@,而且在双引号中展开,每个变量名展开为独立的单词。
如果用的是*,而且在双引号中展开,所有变量名展开为一个单词。
# echo ${!BASH@}
BASH BASHOPTS BASHPID BASH_ALIASES BASH_ARGC BASH_ARGV BASH_CMDS BASH_COMMAND BASH_LINENO BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION
# for i in "${!BASH*}"; do echo "haha"; done
haha <=== 展开为1个单词,所以打印一次。
# for i in "${!BASH@}"; do echo "haha"; done
haha <=== 展开为独立个单词,所以打印多次。
haha
haha
haha
haha
haha
haha
haha
haha
haha
haha
haha
haha
'${!NAME[@]}'
'${!NAME[*]}'
如果 NAME 是数组变量,展开为数组索引(key)的列表。
如果 NAME 不是数组变量,当变量存在时展开为0。
如果用的是@,而且在双引号中展开,每个key展开为独立的单词。
'${#PARAMETER}'
展开为参数值的长度。
如果参数是“*”或者“@”,展开为位置参数的个数。
当参数是以@或*为下标的数组,展开为数组中元素的个数。
'${PARAMETER#WORD}'
'${PARAMETER##WORD}'
将 WORD 的展开结果作为匹配模式(如同文件名展开中的通配符匹配),去匹配参数值的开始部分,匹配到的部分将被删除。
使用“#”,表示最短匹配,
使用“##”,表示最长匹配。
如果参数是“*”或者“@”,对每个位置参数做上面的操作,
当参数是以@或*为下标的数组,对每个数组变量做如上的操作。
'${PARAMETER%WORD}'
'${PARAMETER%%WORD}'
将 WORD 的展开结果作为匹配模式(如同文件名展开中的通配符匹配),从参数值的尾部做匹配,匹配到的部分将被删除。
使用“#”,表示最短匹配,
使用“##”,表示最长匹配。
如果参数是“*”或者“@”,对每个位置参数做上面的操作,
当参数是以@或*为下标的数组,对每个数组变量做如上的操作。最终结果为所有被处理过的参数的列表。
'${PARAMETER/PATTERN/STRING}'
这个可视为上面两个展开的增强版。PATTERN 的展开结果作为匹配模式,对参数的值做最长匹配,匹配的部分替换为 STRING。
一般只有第一个被匹配的部分才替换为 STRING,但如果 PATTERN 以 '/' 开头,则所有匹配的部分都将被替换。
如果 PATTERN 以 '#' 开头,它必须从参数值的开始部分进行匹配。
如果 PATTERN 以 '%' 开头,它必须从参数值的尾部进行匹配。
如果 STRING=null,匹配的部分将被删除,而且 PATTERN 后面的 '/' 被忽略。
如果参数是“*”或者“@”,对每个位置参数做上面的操作,
当参数是以@或*为下标的数组,对每个数组变量做如上的操作。最终结果为所有被处理过的参数的列表。
'${PARAMETER^PATTERN}'
'${PARAMETER^^PATTERN}'
'${PARAMETER,PATTERN}'
'${PARAMETER,,PATTERN}'
这个展开用来修改参数值的字符的大小写。
PATTERN 的展开结果作为匹配模式
^ 将匹配的第一个字母的小写改为大写。
, 将匹配的第一个字母的大写改为小写。
^^ 将匹配的所有字母改为大写
,, 将匹配的所有字母改为小写
如果没有给出 PATTERN,则默认将 PATTERN 设为 ?,表示匹配任意一个字符。
如果参数是“*”或者“@”,对每个位置参数做上面的操作,
当参数是以@或*为下标的数组,对每个数组变量做如上的操作。最终结果为所有被处理过的参数的列表。
3.5.4 命令替换
命令替换允许使用命令的输出替换命令本身。
命令替换有两种写法:
$(COMMAND) 或者 `COMMAND`
命令替换按如下方式进行:
1,执行命令,使用命令的标准输出替换上述的命令替换表达式。
2,如果命令输出的尾部有跟着换行符,就将换行符删除。
3,嵌入在命令输出之中的换行符这时不会删除,但可能在进行单词分割的时候被删除。
'$(cat FILE)' 与 '$(< FILE)' 是相同的,但后者执行速度更快。
使用旧式的反引号 '`' 时,反斜线 '' 失去特殊意义,仅当反斜线后跟 '$','`','' 时,反斜线对其进行转义,否则只是普通字符。
使用 '$(COMMAND)' 时,圆括号内的所有字符不做特殊对待。
命令替换可进行嵌套,使用 '`' 形式的命令替换时,内部的 '`' 要使用反斜线 '' 转义。
当命令替换在双引号 "" 中进行时,替换的结果不再进行单词分割和文件名展开。
3.5.5 算术展开
算术展开允许对算术表达式进行计算,并使用计算结果替换算术展开的整个表达式。
其语法为:
$(( EXPRESSION ))
对于 EXPRESSION 的处理,如同使用了双引号 '"' 将之引用,大多数的特殊字符失去特殊意义,规则参考前面专门的章节。这里特殊的一点是,EXPRESSION 中的 '"' 不做特殊对待。
EXPRESSION 中的所有 token 可能进行:参数展开,命令替换,引用取消。
算术展开可进行嵌套。
算术表达式的计算规则参见后面的小节。
如果 EXPRESSION 是无效的,bash 打印一条消息到错误输出,替换不再进行。
3.5.6 过程替换
如果系统支持命名管道(named pipes: FIFOs),或者支持命名的打开文件的 '/dev/fd' 方法,过程替换也被支持。
其形式如下:
<(LIST) 或者 >(LIST)
过程 LIST 执行时,其输出或输入与 FIFO 或者 '/dev/fd' 下的文件相连。相连的文件名被当做一个参数传递给当前命令。
使用 '>(LIST)' 时,对文件写入,相同于给 LIST 提供输入。
使用 '<(LIST)' 时,LIST的输出写入文件,读取文件,可以获得 LIST 的输出。
注意 '<' 或 '>' 与圆括号之间没有空格,否则整个构造被解释为重定向。
过程替换可用时,会与 参数和变量展开,命令替换,算术展开同步进行。
3.5.7 单词分割
shell 对 参数展开,命令替换,算术展开的结果进行扫描。对未被双引号引用的部分进行单词分割。
shell 把 '$IFS' 中的每一个字符当做分隔符,对其他展开生成的结果进行单词分割。
如果 'IFS' 没有设置,或者其值正好是默认值 '<space><tab><newline>',之前进行的展开生成的结果的开始部分和结束部分,如果是由这三个字符组成的连续字符串,将不做处理。在中间的话,会发挥单词分隔符的作用。
如果 'IFS' 的值不是默认值,只要空白字符在 'IFS' 之中(这时的空白符可称为 IFS 空白符),那么由空白字符(space 和 tab)组成的连续字符串,出现在展开结果的开始部分和结束部分时,被忽略不做处理。
任何在 'IFS' 中的非空白符,与毗连的 'IFS' 空白符一起作为分隔符使用。单词中的 'IFS' 空白符字符串也作为分隔符使用。如果 'IFS' 是空字符串,不进行单词分割。
显式出现的空字符串("" 或者 '')将被保留。由参数值为空的参数展开生成的未被引号引用的隐式空字符串,将被删除。如果参数值为空的参数展开在双引号 "" 中进行,空字符串被保留。
如果之前没有进行任何展开,单词分割也不会进行。
3.5.8 文件名展开
目录:
3.8.1 模式匹配 shell 如何进行模式匹配
完成单词分割之后,除非设置了 '-f' 选项(set 命令),bash 依次扫描每个单词,寻找 '*', '?' 和 '[',如果出现其中一个,该单词被认为是一个 PATTERN,并替换为一组按字典序排序的匹配的文件名。
如果没找到匹配的文件名,而且 shell 选项 'nullglob' 是关闭的,该单词保留原样。
如果没找到匹配的文件名,而且 shell 选项 'nullglob' 是开启的,该单词被删除。
如果没找到匹配的文件名,而且 shell 选项 'failglob' 是开启的,打印一条错误信息,不执行命令。
如果 shell 选项 'nocaseglob' 开启,匹配时忽略大小写。
当一个 pattern 用作文件名展开时,字符 '.' 位于文件名首部的 或者'.' 跟在 '/' 之后('/.')时,必须进行显式地匹配,除非开启了 'dotglob' 选项。
匹配文件名时,字符 '/' 必须进行显式地匹配。除此之外,字符 '.' 不被特殊对待。
参见 'shopt' 命令详细了解 'nocaseglob','nullglob','failglob' 以及 'dotglob' 选项。
shell 变量 'GLOBIGNORE' 可用于限制文件名的匹配。
如果设置了这个变量,每个匹配的文件名如果同时匹配 'GLOBIGNORE' 中的 pattern,这个文件名从匹配结果列表中删除。
'GLOBIGNORE' 变量被设置且非空时,文件名 '.' 和 '..' 总是被忽略。
'GLOBIGNORE' 变量设置为非空值时,'dotglob' 选项被开启,所以所有其他以 '.' 开始的文件名也会被匹配。
如果想要忽略以 '.' 开始的文件名,可将 '.*' 设置为 'GLOBIGNORE' 变量的其中一个 pattern。'GLOBIGNORE' 变量未设置时,'dotglob' 选项被关闭。
3.5.8.1 模式匹配
pattern 中的字符,除了下面描述的特殊 pattern 字符,将匹配其自身。NUL 字符不可出现在 pattern 中。
'' 字符将下面的特殊字符转义为普通字符,以匹配其自身,匹配时,'' 字符自身被忽略。
希望特殊字符匹配其自身时,必须将其引用(quoted)。
'*'
匹配任意字符串,包括 空字符串。
shell 选项 'globstar' 开启时,'**' pattern 匹配所有文件和 0+ 个目录及子目录。
'**/' 只匹配目录和子目录。
'?'
匹配任意单个字符
'[...]'
匹配括号中任意一个字符。
一对以 '-' 分隔的字符表示 RANGE EXPRESSION(范围表达式)。匹配这对字符之间(包括这对字符)的任意一个字符。
如果左括号 '[' 后面第一个字符是 '!' 或者 '^',匹配括号内所有字符之外的
任意一个字符。
'-' 字符放在第一个位置,或最后一个位置时,可以被匹配。
']' 字符放在第一个位置,可以被匹配。
范围表达式的排序顺序由当前 locale 语系 和 'LC_COLLATE' (如果已设置)的值决定。
比如,根据默认的 C locale,'[a-dx-z]' 等效于 '[abcdxyz]'。许多其他 locale 按照字典序排序,这时 '[a-dx-z]' 可能等效于 '[aBbCcDdxXyYz]'。
可通过设置 'LC_COLLATE' 或 'LC_ALL' 为 'C',强制使用传统的 'C' 语系排序。
在中括号中,CHARACTER CLASSES(特定字符集)可由 '[:'CLASS':]' 指定。
POSIX 标准定义的 CLASS 包括:
alnum alpha ascii blank cntrl digit graph
lower print punct space upper word xdigit
特定字符集可匹配属于该字符集的任意一个字符。'word' 匹配 字母,数字 和 下划线 '_'。
Within [ and ], an EQUIVALENCE CLASS can be specified usingthe syntax [=C=], which matches all characters with the same collation weight (as defined by the current locale) as the character C.
Within [ and ], the syntax [.SYMBOL.] matches the collating symbol SYMBOL.
如果 shell 选项 'extglob' 开启(使用 'shopt' 命令),可使用下列的扩展 pattern。在下面的描述中, PATTERN-LIST 是由 '|' 分隔的一个
或多个 pattern 组成的列表。
复合 pattern 可由下面的一个或多个 pattern 组成。
'?(PATTERN-LIST)'
匹配 0 个或 1个 给出的 patterns。
'*(PATTERN-LIST)'
匹配 0 个或 多个 给出的 patterns。
'+(PATTERN-LIST)'
匹配 1 个或 多个 给出的 patterns。
'@(PATTERN-LIST)'
匹配给出的 patterns 的其中一个。
'!(PATTERN-LIST)'
匹配给出的 patterns 之外的任意字符串。
3.5.9 引用取消
前面的展开完成之后,所有未被引用的 \,' 和 ",除了由之前的展开生成的,都被删除。