Bash 转义和引号的使用
字面量、转义和引号是任何编程语言的基础,但却少有人认真地学习 Bash 中的转义和 引号。 好消息是 Bash 引号的语义非常简单,这篇文章就可以完全描述。 TL;DR:
- 反斜线用来转义除换行之外的所有字符,反斜线加换行为连行;
- 单引号用来直出字面量,其内容部分不允许转义,包括单引号转义也不允许;
- 双引号内允许 命令替换 和对特定几个字符转义,双引号内的反斜线对其他字符没有特殊含义,会被当作字面量处理。
一个例子
如果不熟悉 Bash 引号的语义,尤其是配合管道和 xargs 等命令时,事情会变得很复杂很难以理解。
比如下面的命令把 16 进制 ASCII 转为字符串 harttle
:
echo '\\x68\\x61\\x72\\x74\\x74\\x6c\\x65' | xargs -0 printf '%b'
如果没有加引号或没有加 -0
都不会得到正确的结果,二者都会影响参数如何转义。
在介绍完规则后我们重新来看这个例子。
Bash 转义
Bash(Posix)转义规则很简单:
一、反斜线用来保持字面量。Bash 里反斜线用来转义下一个字符,保持下一个字符的字面值。
比如 \$
表示字面量 $
,否则如果没有反斜线 $
会被 Bash 参数展开。
例如:
# echo 将收到字面量 \x68\x61\x72\x74\x74\x6c\x65,下一个字符 \ 被保持
# 输出 harttle
echo \\x68\\x61\\x72\\x74\\x74\\x6c\\x65
# echo 将收到字面量 x68x61x72x74x74x6cx65,下一个字符 x 被保持
# 输出:x68x61x72x74x74x6cx65
echo \x68\x61\x72\x74\x74\x6c\x65
注意 Shell 只负责处理参数和调用命令,不会识别 \t
, \n
,\x68
等其他编程语言里的 ASCII 特殊字符,这些特殊字符的处理通常在具体的软件中,比如 echo
, printf
等。
例如下面的命令会输出 a b
:
echo 'a\tb'
但 \t
的语义并不是由 Shell 表达的,Shell 只是把这个长度为 4 的字符串 "a\tb"
传递给 echo
程序,后者将会收到参数 argv[1] === "a\\tb"
。
二、反斜线+换行例外。反斜线后一个字符是换行(<NL>
)时上一条规则例外。
这时 \<NL>
表示连行,一个命令可以分行写。换句话说 \<NL>
等效于会在解析时删除。
比如:
cat log.txt |\
grep a |\
grep b
# 等价于
cat log.txt | grep a | grep b
单引号的使用
单引号用来保持引用内容的所有字面量,包括反斜线。也就是说一对单引号中不得出现单引号,它前面有反斜线也不行。
例如下面的命令将会输出 harttle
:
# echo 将收到字面量 \x68\x61\x72\x74\x74\x6c\x65
echo '\x68\x61\x72\x74\x74\x6c\x65'
如果单引号之间出现单引号,引用内容立即结束(来自其他编程语言的同学注意)。 比如:
$ echo 'foo\'bar' # 回车
quote> # Shell 继续等待输入,因为第一个引用内容是 foo\
# 紧接着是字面量 bar,然后是一个未关闭的 '
quote>' # 输入 ' 结束第二个引用内容(为空字符串)并回车
foar # echo 收到的输入为 foo\bar,\b 被 echo 解释为退格
这个情况经常在尝试转义单引号里的单引号时发生,比如 sed 's/\'/"/g'
, grep 'harttle\'s'
都是错误的写法。
双引号的作用
双引号也是保持引用内容的字面量,但 $
, `
, \
除外(POSIX 标准)。
其中:
Shell 转义奇怪的是反斜线后是 $
, `
, "
, \
, <NL>
时反斜线才表示转义,否则反斜线没有特殊含义(表示一个反斜线字面量)。
例如下面两个命令都会输出 harttle
,因为 "\\x"
的第一个反斜线表示转义,解释为 "\x"
,而 "\x"
中的反斜线没有特殊含义,也解释为 "\x"
。
echo "\\x68\\x61\\x72\\x74\\x74\\x6c\\x65"
echo "\x68\x61\x72\x74\x74\x6c\x65"
其中 \<NL>
和单引号一样表示连行,\"
表示字面量双引号,注意这在单引号语法中是不允许的。
案例分析
现在继续看本文刚开始的例子:
echo '\\x68\\x61\\x72\\x74\\x74\\x6c\\x65' | xargs -0 printf '%b'
- 由于 echo 的参数使用单引号,echo 收到的参数为字面量
\\x68\\x61\\x72\\x74\\x74\\x6c\\x65
。 - 因此
echo '\\x68\\x61\\x72\\x74\\x74\\x6c\\x65'
的输出为:\x68\x61\x72\x74\x74\x6c\x65
。 - 由于
xargs -0
下标准输入会被当做字面量处理(\
不再是特殊字符),xargs
给到printf
的第二个参数为字面量\x68\x61\x72\x74\x74\x6c\x65
,第一个参数为%b
。 printf
处理十六进制 ASCII 字面量语法,输出harttle
。
有两点需要注意:
- 如果 echo 的第一个参数只有单个反斜线(
\x68\x61\x72\x74\x74\x6c\x65
),echo 的输出即为harttle
,经过printf
后仍然为harttle
; - 如果
xargs
没有添加-0
参数,xargs
会把它的标准输入正常做 Bash 转义,也就是说xargs
给到printf
的第二个参数将会是x68x61x72x74x74x6cx65
,因为 Bash 转义中\x
的语义(见“转义”一节)和printf
转义中\x
的语义不同。
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2020/06/26/bash-quote-escape.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。