美文网首页脚本Shell展开
3.shell 基本特性之~ shell展开详解

3.shell 基本特性之~ shell展开详解

作者: C86guli | 来源:发表于2016-08-22 08:03 被阅读873次

    第 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 引用取消


    前面的展开完成之后,所有未被引用的 \,' 和 ",除了由之前的展开生成的,都被删除。

    相关文章

      网友评论

        本文标题:3.shell 基本特性之~ shell展开详解

        本文链接:https://www.haomeiwen.com/subject/glbwsttx.html