进程 argv 中的意外字符串转义

Unexpected strings escape in process argv

有点惊讶:

$ node -p 'process.argv' $SHELL '$SHELL' \t '\t' '\t'
[ 'node', '/bin/bash', '$SHELL', 't', '\t', '\\t' ]

$ python -c 'import sys; print sys.argv' $SHELL '$SHELL' \t '\t' '\t'
['-c', '/bin/bash', '$SHELL', 't', '\t', '\\t']

预期与以下行为相同:

$ echo $SHELL '$SHELL' \t '\t' '\t'
/bin/bash $SHELL t \t \t

这就是我需要传递内容的方式。

为什么在进程 argv 中使用 '\t''\t' 进行额外的转义?为什么处理方式与 '$SHELL' 不同?这实际上是从哪里来的?为什么不同于 echo 行为?

首先我认为这是 minimist 部分的一些 extras,但后来发现 Node.js 和 [=30= 都一样].这里可能遗漏了一些明显的东西。

使用$'...'形式在BASH中传递转义序列,如\t\n\r[=16=]等:

python -c 'import sys; print sys.argv' $SHELL '$SHELL' \t $'\t' $'\t'
['-c', '/bin/bash', '$SHELL', 't', '\t', '\t']

根据man bash

Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows:

\a     alert (bell)
\b     backspace
\e
\E     an escape character
\f     form feed
\n     new line
\r     carriage return
\t     horizontal tab
\v     vertical tab
\     backslash
\'     single quote
\"     double quote
\nnn   the eight-bit character whose value is the octal value nnn (one to three digits)
\xHH   the eight-bit character whose value is the hexadecimal value HH (one or two hex digits)
\uHHHH the Unicode (ISO/IEC 10646) character whose value is the hexadecimal value HHHH (one to four hex digits)
\UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value is the hexadecimal value HHHHHHHH  (one  to  eight  hex digits)
\cx    a control-x character

在 python 和 node.js 中,print 使用标量字符串的方式与使用集合的方式不同。

字符串被简单地打印为字符序列。结果输出一般是用户期望看到的,但不能作为字符串在语言中的表示。但是当一个 list/array 被打印出来时,你得到的是一个有效的 list/array 文字,它可以在程序中使用。

例如,在python中:

>>> print("x")
x
>>> print(["x"])
['x']

打印字符串时,您只看到字符。但是当打印包含字符串的列表时,python 添加引号字符,因此输出是一个有效的列表文字。同样,如果需要,它会添加反斜杠:

>>> print("\")
\
>>> print(["\"])
['\']

node.js 的工作方式完全相同:

$ node -p '"\"'
\
$ node -p '["\"]'
[ '\' ]

当您打印包含单个反斜杠的字符串时,您只会得到一个反斜杠。但是当你打印一个包含由单个反斜杠组成的字符串的 list/array 时,你会得到一个带引号的字符串,其中反斜杠用反斜杠转义,允许它在程序中用作文字。

与在节点和 python 中打印字符串一样,标准 echo shell 实用程序只打印字符串中的实际字符。在标准 shell 中,没有类似于节点和 python 数组打印的机制。然而,Bash 确实提供了一种机制,可以以一种可以用作 bash 程序的一部分的格式打印出变量的值:

$ quote=\"
# $quote is a single character:
$ echo "${#quote}"
1
# $quote prints out as a single quote, as you would expect
$ echo "$quote"
"
# If you needed a representation, use the 'declare' builtin:
$ declare -p quote
declare -- quote="\""
# You can also use the "%q" printf format (a bash extension)
$ printf "%q\n" "$quote"
\"

(参考:bash declare and printf 手册。或在 bash 会话中键入 help declarehelp printf。)


不过,这还不是全部。了解 shell 如何解释您键入的内容也很重要。换句话说,当你写

some_utility  \" "\"" '\"'

some_utility 实际上在 argv 数组中看到了什么?

在标准 shell(包括 bash)的大多数上下文中,像 \t 这样的 C 风格转义序列不会被这样解释。 (当这些序列出现在格式字符串中时,标准 shell 实用程序 printf 会解释这些序列,一些其他标准实用程序也会解释这些序列,但 shell 本身不会。)处理标准 shell 的反斜杠取决于上下文:

  • 不带引号的字符串:反斜杠引用后面的字符,无论它是什么(除非它是换行符,在这种情况下反斜杠和换行符都从输入中删除)。

  • 双引号字符串:反斜杠可以用来转义字符$,\,", `; 此外,从输入中删除了一个反斜杠后跟一个换行符,就像在一个不带引号的字符串中一样。在 bash 中,如果 history启用扩展(因为它在 interactive shells 中默认启用),反斜杠也可用于避免 ! 的历史扩展, 但反斜杠保留在最后的字符串中。

  • 单引号字符串:反斜杠被视为普通字符。 (因此,无法在单引号字符串中包含单引号。)

Bash 增加了两个引用机制:

  • C 风格引用,$'...'。如果单引号字符串前面有一个美元符号,那么字符串 中的 C 风格转义序列将被 以与 C 编译器大致相同的方式解释。这包括标准空白字符,例如换行符 (\n)、八进制、十六进制和 Unicode 转义字符 (0\x0a\u000A\U0000000A),以及一些非 C 序列,包括 "control" 字符(\cJ)和 ESC 字符 \e\E(与 \x1b 相同)。反斜杠也可以用来转义\'"。(注意这是不同的list from the list of backslashable characters in double-quoted strings; 这里,美元符号或反引号前的反斜杠是 not 特殊的,而单引号前的反斜杠是特殊的;此外,不解释反斜杠换行序列。)

  • 特定于语言环境的翻译:$"..."。如果双引号字符串前面有美元符号,则反斜杠(以及变量扩展和命令替换)被解释为普通双引号字符串,然后在由当前语言环境确定的消息目录中查找该字符串。

(参考文献:Posix standard, Bash manual。)