转义和引号

警告
本文最后更新于 2023-10-30,文中内容可能已过时。

转义和引号

Bash 只有一种数据类型,就是字符串。不管用户输入什么数据,Bash 都视为字符串。因此,字符串相关的引号和转义,对 Bash 来说就非常重要。

转义和反斜杠

某些字符在 Bash 里面有特殊含义(比如$&*)。Bash 在碰到这些字符的时候,会将其理解为其他意思,比如$name表示的是name这个变量,如果想要让 Bash 将其当作一个普通字符,不对其进行转义,就必须在这些特殊字符前面加上反斜杠,使其变成普通字符。这就加上反斜杠的操作叫做“转义”(escape)。

1
2
3
4
5
6
7
8
$ echo $name

$ echo *
aaa bbb ccc test.txt
$ echo \$name
$name
$ echo \*
*

因为 name 变量在当前环境没有定义,所以输出为空。

反斜杠本身也是特殊字符,想要输出反斜杠,也需要进行转义,\\

1
2
$ echo \\
\

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

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

如果想要在命令行中让这些不可打印字符表达出其特殊含义,可以把它们放在引号里面,然后使用echo命令的-e参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ echo a\tb
atb
$ echo "a\tb"
a\tb
$ echo -e "a\tb"
a       b
$ echo -e "a\bb"
b
$ echo -e "a\nb"
a
b

换行符是一个特殊字符,表示命令的结束,Bash 收到这个字符以后,就会对输入的命令进行解释执行。换行符前面加上反斜杠转义,就使得换行符变成一个普通字符,即\\n,Bash 会将其当作长度为0的空字符处理,从而可以将一行命令写成多行。这也是为什么通过在命令的末尾写\可以将一行命令拆分成多行

1
2
3
4
$ echo hello \
> world \
> xiashuo.xyz
hello world xiashuo.xyz

单引号

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ echo '$'
$
$ echo '$name'
$name
$ echo '*'
*
$ echo '$(ls)'
$(ls)
$ echo '$((2+2))'
$((2+2))
$ echo '\'
\

由于反斜杠在单引号里面变成了普通字符,所以如果单引号之中,还要使用单引号,不能使用转义,需要在外层的单引号前面加上一个美元符号($),然后再对里层的单引号转义。不过这种下,我们一般在双引号中使用单引号

1
2
3
4
5
6
7
8
$ echo 'it's'
> ^C
$ echo $'it's'
> ^C
$ echo $'it\'s'
it's
$ echo "it's"
it's

双引号

双引号比单引号宽松,大部分特殊字符在双引号里面,都会失去特殊含义,变成普通字符,比如*,这意味着,双引号里面不会进行文件名扩展。但是,三个特殊字符除外:美元符号($)、反引号(`)和反斜杠(\)。这三个字符在双引号之中,依然有特殊含义,会被 Bash 自动扩展。

在双引号中,美元符号用来引用变量,反引号则是执行子命令,反斜杠则是用来转义,以反斜杠开头的不可打印字符则会失效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ echo file{1..2}
file1 file2
$ echo "file{1..2}"
file{1..2}
$ echo "*"
*
$ echo "shell path: $SHELL"
shell path: /bin/bash
$ echo "date now : `date`"
date now : Mon Aug 14 17:19:14 CST 2023
$ echo "\"hello\" \"world\""
"hello" "world"

通过回车插入的换行符在双引号之中,会失去特殊含义,Bash 正常情况下会将换行符解释为命令结束,但是换行符在双引号之中就失去了这种特殊作用,只用来换行,所以可以输入多行。echo命令会将换行符原样输出,显示的时候正常解释为换行。所以可以利用双引号,在命令行输入多行文本。

1
2
3
4
5
6
7
$ echo "hello
>
> world
> "
hello

world

但是你直接在双引号中手写\n是不行的

1
2
$ echo "hello\nworld"
hello\nworld

双引号的另一个常见的使用场合是,文件名包含空格。这时就必须使用双引号(或单引号),将文件名放在里面。否则 Bash 会将其理解为被被空格分割开的两个参数

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ cal
     August 2023
Su Mo Tu We Th Fr Sa
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

$ echo $(cal)
August 2023 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
$ echo "$(cal)"
     August 2023
Su Mo Tu We Th Fr Sa
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

Here 文档

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

1
2
3
<< token
text
token

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

token 可以理解开始输入和结束输入的文本标记,阮一峰大佬说这是 Here 文档的名称,其实不合适

1
2
3
4
5
6
7
8
$ cat << file
> 111
> 222
> 333
> file
111
222
333

我们在 shell 脚本中创建文件并在其中填入内容的时候,经常使用 Here 文档。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cat > test.txt << _
> <html>
> <div>
> </div>
> </html>
> _
$ cat test.txt
<html>
<div>
</div>
</html>

在 Here 文档内部会发生变量解析,同时支持反斜杠转义,但是不支持通配符扩展,双引号和单引号也失去语法作用,变成了普通字符。

支持变量解析,让我们可以在脚本中根据变量动态地生成 Here 文档。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ cat << EOF
> $name
> "$name"
> '$name'
> \$name
> *
> EOF
xiashuo.xyz
"xiashuo.xyz"
'xiashuo.xyz'
$name
*

如果不希望发生变量替换,可以把 Here 文档的开始标记放在单引号之中。(结束标记不用放到单引号中)

Here 文档的本质是重定向,它将文档重定向给了某个命令的标准输入,所以,Here 文档只适合那些可以接受标准输入作为参数的命令,比如catgrep,有的命令不接受标准输入作为参数,比如echo,所以echo命令无法配合 Here 文档。

关于标准输入跟命令行参数有什么区别,请看《Linux 文件系统基础》的| 管道符号小节

关于什么是重定向,请看《Linux 文件系统基础》的>指令和>>指令小节

重定向和管道还是有区别的,阮一峰大佬的博客中写到重定向等同于管道的说法是错误的

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

Here 字符串

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

1
<<< string

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

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

1
2
3
4
5
6
7
8
$ cat hello world
cat: hello: No such file or directory
cat: world: No such file or directory
$ cat <<< 'hello world'
hello world
# 效果等同于 echo
$ echo hello world
hello world

效果等同于echo hello world

0%