引号和转义

    某些字符在 Bash 里面有特殊含义(比如、&*)。

    上面例子中,输出$date不会有任何结果,因为$是一个特殊字符。

    如果想要原样输出这些特殊字符,就必须在它们前面加上反斜杠,使其变成普通字符。这就叫做“转义”(escape)。

    1. $ echo \$date
    2. $date

    上面命令中,只有在特殊字符$前面加反斜杠,才能原样输出。

    反斜杠本身也是特殊字符,如果想要原样输出反斜杠,就需要对它自身转义,连续使用两个反斜线(\\)。

    1. $ echo \\
    2. \

    上面例子输出了反斜杠本身。

    反斜杠除了用于转义,还可以表示一些不可打印的字符。

    • \a:响铃
    • \b:退格
    • \n:换行
    • \r:回车
    • \t:制表符

    如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数。

    1. $ echo a\tb
    2. atb
    3. $ echo -e "a\tb"
    4. a b

    上面例子中,命令行直接输出不可打印字符,Bash 不能正确解释。必须把它们放在引号之中,然后使用echo命令的-e参数。

    由于反斜杠可以对换行符转义,使得 Bash 认为换行符是一个普通字符,从而可以将一行命令写成多行。

    1. $ mv \
    2. /path/to/foo \
    3. /path/to/bar
    4. # 等同于
    5. $ mv /path/to/foo /path/to/bar

    上面例子中,如果一条命令过长,就可以在行尾使用反斜杠,将其改写成多行。这是常见的多行命令的写法。

    Bash 允许字符串放在单引号或双引号之中,加以引用。

    单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号(*)、美元符号($)、反斜杠(\)等。

    1. $ echo '*'
    2. *
    3. $ echo '$USER'
    4. $USER
    5. $ echo '$((2+2))'
    6. $((2+2))
    7. $ echo '$(echo foo)'
    8. $(echo foo)

    上面命令中,单引号使得 Bash 扩展、变量引用、算术运算和子命令,都失效了。如果不使用单引号,它们都会被 Bash 自动扩展。

    1. # 不正确
    2. $ echo it's
    3. $ echo 'it\'s'
    4. # 正确
    5. $ echo $'it\'s'

    不过,更合理的方法是改在双引号之中使用单引号。

    1. $ echo "it's"
    2. it's

    双引号比单引号宽松,可以保留大部分特殊字符的本来含义,但是三个字符除外:美元符号($)、反引号(` )和反斜杠(\)。也就是说,这三个字符在双引号之中,会被 Bash 自动扩展。

    上面例子中,通配符*放在双引号之中,就变成了普通字符,会原样输出。这一点需要特别留意,双引号里面不会进行文件名扩展。

    1. $ echo "$SHELL"
    2. /bin/bash
    3. $ echo "`date`"
    4. Mon Jan 27 13:33:18 CST 2020

    上面例子中,美元符号和反引号在双引号中,都保持特殊含义。美元符号用来引用变量,反引号则是执行子命令。

    1. $ echo "I'd say: \"hello!\""
    2. I'd say: "hello!"
    3. $ echo "\\"
    4. \

    上面例子中,反斜杠在双引号之中保持特殊含义,用来转义。所以,可以使用反斜杠,在双引号之中插入双引号,或者插入反斜杠本身。

    由于双引号将换行符解释为普通字符,所以可以利用双引号,在命令行输入多行文本。

    1. $ echo "hello
    2. world"
    3. hello
    4. world

    上面命令中,Bash 正常情况下会将换行符解释为命令结束,但是换行符在双引号之中就是普通字符,所以可以输入多行。echo命令会将换行符原样输出,显示的时候正常解释为换行。

    双引号的另一个常见的使用场合是,文件名包含空格。这时就必须使用双引号,将文件名放在里面。

    1. $ ls "two words.txt"

    上面命令中,two words.txt是一个包含空格的文件名,否则就会被 Bash 当作两个文件。

    双引号会原样保存多余的空格。

    1. $ echo "this is a test"
    2. this is a test

    双引号还有一个作用,就是保存原始命令的输出格式。

    1. # 单行输出
    2. $ echo $(cal)
    3. 一月 2020 1 2 3 ... 31
    4. # 原始格式输出
    5. $ echo "$(cal)"
    6. 一月 2020
    7. 1 2 3 4
    8. 5 6 7 8 9 10 11
    9. 12 13 14 15 16 17 18
    10. 19 20 21 22 23 24 25
    11. 26 27 28 29 30 31

    上面例子中,如果$(cal)不放在双引号之中,echo就会将所有结果以单行输出,丢弃了所有原始的格式。

    Here 文档(here document)是一种输入多行字符串的方法,格式如下。

    1. << token
    2. token

    它的格式分成开始标记(<< token)和结束标记()。开始标记是两个小于号 + Here 文档的名称,名称可以随意取,后面必须是一个换行符;结束标记是单独一行顶格写的 Here 文档名称,如果不是顶格,结束标记不起作用。两者之间就是多行字符串的内容。

    Here 文档内部会发生变量替换和通配符扩展,但是双引号和单引号都失去语法作用,变成了普通字符。

    1. $ foo='hello world'
    2. $ cat << _example_
    3. $foo
    4. "$foo"
    5. '$foo'
    6. _example_
    7. hello world
    8. "hello world"
    9. 'hello world'

    上面例子中,变量$foo发生了替换,但是双引号和单引号都原样输出了,表明它们已经失去了引用的功能。

    如果不希望发生变量替换和通配符扩展,可以把 Here 文档的开始标记放在单引号之中。

    1. $ foo='hello world'
    2. $ cat << '_example_'
    3. $foo
    4. "$foo"
    5. '$foo'
    6. _example_
    7. $foo
    8. "$foo"
    9. '$foo'

    上面例子中,Here 文档的开始标记(_example_)放在单引号之中,导致变量替换失效了。

    Here 文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了echo命令。

    1. $ command << token
    2. string
    3. token
    4. # 等同于
    5. $ echo string | command

    上面代码中,Here 文档相当于echo命令的重定向。

    所以,Here 字符串只适合那些可以接受标准输入作为参数的命令,对于其他命令无效,比如echo命令就不能用 Here 文档作为参数。

    1. $ echo << _example_
    2. hello
    3. _example_

    上面例子不会有任何输出,因为 Here 文档对于echo命令无效。

    此外,Here 文档也不能作为变量的值,只能用于命令的参数。

    Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<)表示。

    1. <<< string

    它的作用是将字符串通过标准输入,传递给命令。

    有些命令直接接受给定的参数,与通过标准输入接受参数,结果是不一样的。所以才有了这个语法,使得将字符串通过标准输入传递给命令更方便,比如cat命令只接受标准输入传入的字符串。

    1. $ cat <<< 'hi there'
    2. # 等同于
    3. $ echo 'hi there' | cat

    上面的第一种语法使用了 Here 字符串,要比第二种语法看上去语义更好,也更简洁。

    1. $ md5sum <<< 'ddd'
    2. # 等同于
    3. $ echo 'ddd' | md5sum

    上面例子中,md5sum命令只能接受标准输入作为参数,不能直接将字符串放在命令后面,会被当作文件名,即md5sum ddd里面的ddd会被解释成文件名。这时就可以用 Here 字符串,将字符串传给命令。