Bash高级编程:命令行扩展

引号:

Shell首先会根据IFS变量把命令行中未被引号引入的部分拆分成一个个word(IFS默认为空格、制表符、换行)。若空格被\转义,则会被视为一个word中的一部分。

双引号中可以随意包含单引号,单引号中也可以随意包含双引号。但是双引号要被转义之后才能被双引号包含。而单引号中的任何字符都不会被视为特殊字符,因此单引号中没办法再包含单引号了,即使被转义。一个转义的单引号也会提前结束前面的那个单引号。若需要在单引号中包含单引号,即正确转义,则需借助语法:

echo $'hello \'world\''   # hello 'world'

一、大括号扩展(非 POSIX 标准)

未被引号引起来的大括号中,若有以逗号(,)连接的列表,或者用双点号(..)指定的范围,则会被扩展为独立的参数(逗号和双点前后都不能有空格,且逗号和双点不能同时使用):

echo {1,2,3}   # 1 2 3
echo {a..c}    # a b c

该扩展可以被嵌套:

echo {{1,2,3},{a..c}}   # 1 2 3 a b c

该扩展还可以产生迪卡尔集:

echo g{e,o}t
# get
# got

echo a{1..3}
# a1
# a2
# a3

echo {a..c}{1..2}
# a1
# a2
# b1
# b2
# c1
# c2

二、波浪号扩展

未被引号引起来的波浪号会被扩展为用户的主目录。波浪号后还可以跟一个合法的用户名以指定用户。若用户名不合法则不被扩展:

echo ~          # 会扩展
echo ~root      # 会扩展
echo ~invalid   # 不会扩展

三、变量扩展

变量扩展使用$符号将一个变量扩展为它的内容:

a=abc
echo $a     # abc
echo ${a}   # abc
echo $a_d   # abc_d

未被双引号引起来的变量扩展,结果还会经历单词分割以及路径扩展。

四、算术扩展

Bash遇到$(())表达式时,会以算术运算来计算该表达式:

echo $(( 1 + 2 * 3 ))   # 7

未被双引号引起来的算术扩展,结果还会经历单词分割。

五、命令扩展

命令扩展即将命令用它执行的输出来替换:

echo `date +%C`    # 20
echo $(date +%C)   # 20

未被双引号引起来的命令扩展,结果还会经历单词分割和路径扩展。

六、单词分割

未被引号引起来的序列都会进行单词分割。单词分割基于IFS变量。IFS的默认值是$' \t\n'

当IFS是默认值或者是unset时,默认IFS中的任何序列都被视为一个单独的分隔符;当IFS包含空白和非空白符时,多个连着的空白符仍被视为一次分隔,然而每个非空白符都被视为一次分隔;当IFS只包含非空白符时,则任何该非空白符以及任何空白符都会视为单独的。

七、路径扩展

未被引号引起来的*, ?, [都会被视为文件匹配符,并且在有文件能正确匹配的前提下,它们会被扩展为匹配到的文件的列表。其中,*匹配任意字符;?匹配某一个字符;[[aeiou], [a-zA-Z], [[:lower:]]的形式进行正则匹配。

八、进程替换

进程替换为进程创建用于重定向IO的临时文件名。它们可以出现在任何需要一个文件名的地方。<(command)表示command输出的文件名;>(command)是command输入的文件名。

进程替换优于|管道的一个理由是不会把一些需要的变量带到子进程中。

九、其他

Bash从Csh继承了!的使用,用于查找历史命令并执行,并且该扩展不能够被双引号取消,只能被单引号取消。比如:

!!     # 执行上一条执行的命令
"!!"   # 同上
'!!'   # 出错
!x     # 执行之前最后一条以x开头的命令
"!x"   # 同上
'!x'   # 出错

但是这也引出了一个问题,即在bash上想显示”hello!”这个字符串,直接这样写是不行的:

echo "hello!"  # !" 要么被扩展成上一条以 " 开头的命令,要么出错

简单的解决办法有两个:

echo 'hello!'    # 以单引号代替双引号
echo "hello! "   # 在 ! 后加上一个空格,避免 ! 被扩展