得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

bash 引号与转义

之前简单的总结过 bash 中单引号与双引号,但是并没有对单双引号的区别进行细致的描述,更多的偏向于解释 shell 特定功能的保留字,今天看到了一篇对于 bash 的单双引号内内容转义讲述更详细的文章,故而更新记形成此记录

bash 引号的语义可由下述三句描述简单归纳:

  1. 反斜线用来转义除换行之外的所有字符,反斜线加换行为连行
  2. 单引号用来直出字面量,其内容部分不允许转义,包括单引号转义也不允许
  3. 双引号允许命令替换和对特定几个字符转义,双引号内的反斜线对其他字符没有特殊含义,会被当作字面量处理

bash (Posix) 转义规则

bash 版本声明,本文后续所有的命令执行在当前 bash 环境中

1
2
3
4
5
6
7
$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

1、反斜线用来保持字面量

bash 里反斜线用来转义下一个字符,保持下一个字符的字面值。例如:

1
2
3
4
5
6
$ echo \$PS2  # \$ 表示字面量 $
$PS2
$ echo $PS2 # 如果没有反斜线 $ 会被 bash 参数展开,即输出该变量的值
>
$ echo a\tb # 下一个字符 t 被保持字面量,\ 被删除
atb

⚠️ 注意:shell 只负责处理参数和调用命令,不会识别 \t, \n,\x68 等其他编程语言里的 ASCII 特殊字符,这些特殊字符的处理通常在具体的软件中,比如 echo, printf 等。 例如:

1
2
$ echo -e 'a\tb'  # \t 的语义并不是由 shell 表达的,shell 只是把这个长度为 4 的字符串 a\tb 传递给 echo 程序,后者将会收到参数 argv[1] === "a\\tb"
a b

2、反斜线 + 换行是例外

反斜线后一个字符是换行时是上一条规则的例外,表示连行,一个命令可以分行写。例如:

1
2
3
4
5
$ echo abc | \
> grep a | \
> grep b
# 等价于
$ echo abc | grep a | grep b

单引号的使用

单引号用来保持引用内容的所有字面量,包括反斜线和换行符;即,一对单引号中不得出现单引号,它前面有反斜线也不行。例如:

1
2
$ echo -e '\x68\x61\x72\x74\x74\x6c\x65'
harttle

如果单引号之间出现单引号,引用内容立即结束。例如:

1
2
3
4
5
6
$ echo -e 'foo\'bar'  # 回车
> # shell 继续等待输入,因为第一个引用内容是 foo\,紧接着是字面量 bar,然后是一个未关闭的 '
> ' # 输入 ' 结束第二个引用内容
foar # echo 收到的输入为 foo\bar<new line><new line>,\b 被 echo 解释为退格,输出第一个 <new line>
# 输出第二个 <new line>
# 输出结束

双引号的作用

双引号也是保持引用内容的字面量,但 $,`,\ 除外(POSIX 标准),其中:

  1. $ 用来做 bash 参数展开
  2. ` 表示命令替换,基本等价于 $()
  3. \ 用来转义

反斜线后是 $,`,",\,换行时反斜线才表示转义,否则反斜线没有特殊含义(表示一个反斜线字面量)。例如:

1
2
3
4
5
6
7
8
9
# "\\x" 的第一个反斜线表示转义,解释为 "\x",而 "\x" 中的反斜线没有特殊含义,也解释为 "\x"
$ echo "\\x68\\x61\\x72\\x74\\x74\\x6c\\x65" # shell 对 \\ 进行转义
\x68\x61\x72\x74\x74\x6c\x65
$ echo -e "\\x68\\x61\\x72\\x74\\x74\\x6c\\x65"
harttle
$ echo "\x68\x61\x72\x74\x74\x6c\x65" # shell 对 \x 并不进行转义
\x68\x61\x72\x74\x74\x6c\x65
$ echo -e "\x68\x61\x72\x74\x74\x6c\x65"
harttle

⚠️ 注意:" 在双引号语法中表示字面量双引号,这在单引号语法中是不允许的

一个例子

如果不熟悉 Bash 引号的语义,尤其是配合管道和 xargs 等命令时,事情会变得很复杂很难以理解。例如:

1
2
3
# 把 16 进制 ASCII 转为字符串 harttle
$ echo -e '\\x68\\x61\\x72\\x74\\x74\\x6c\\x65' | xargs -0 printf '%b'
harttle

转义过程解析:

  1. 由于 echo 的参数使用单引号,echo 收到的参数为字面量 \x68\x61\x72\x74\x74\x6c\x65,因此输出为:\x68\x61\x72\x74\x74\x6c\x65
  2. 由于 xargs -0 下标准输入会被当做字面量处理(\ 不再是特殊字符),xargs 给到 printf 的第二个参数为字面量 \x68\x61\x72\x74\x74\x6c\x65,第一个参数为 %b
  3. printf 第一个参数为 %b,第二个参数作为字符串,并对其内容中的 \ 进行转义处理,十六进制 ASCII 字面量语法,输出 harttle

⚠️ 注意:

  1. 如果 echo 的第一个参数只有单个反斜线(\x68\x61\x72\x74\x74\x6c\x65),echo 的输出即为 harttle,经过 printf 后仍然为 harttle
  2. 如果 xargs 没有添加 -0 参数,xargs 会把它的标准输入正常做 bash 转义,也就是说 xargs 给到 printf 的第二个参数将会是 x68x61x72x74x74x6cx65,因为 bash 转义中反斜线用来转义下一个字符,保持下一个字符的字面值,并删除反斜线

be slow to promise and quick to perform.