这个问题与如何使用 printf 打印反斜杠后跟换行符?,其中 OP 尝试打印\\\n
为单个反斜杠后跟换行符(不是文字\n
)。
\\
虽然根据 shell 规则,将扩展为\
和\n
as 是有意义的n
(即 shell 执行反斜杠转义以保留后续字符的字面形式),但当我执行时,strace
它看起来好像 shell 执行了完全不同的操作,并且我正在努力解释我所看到的。
$ strace -e execve printf "\\\n"
execve("/usr/bin/printf", ["printf", "\\\\n"], [/* 42 vars */]) = 0
\n+++ exited with 0 +++
换句话说,我看到的是,进入系统调用argv
部分的字符execve
数量实际上增加了,并且附加了一个额外的反斜杠,而不是减少了。
传递单引号 '\\\\n'
更加令人困惑:
$ strace -e execve printf '\\\\n'
execve("/usr/bin/printf", ["printf", "\\\\\\\\n"], [/* 42 vars */]) = 0
\\n+++ exited with 0 +++
换句话说,我希望这里的 shell 会将所有未更改的内容传递给之前命令中的printf
内容execve()
,并获得与 相同的输出printf "\\\n"
,但它是不同的。
在某种程度上,我似乎绕着理解纯的 printf
它本身(由系统执行的)会将部分\\\n
中的 参数解释为反斜杠和换行符。同时,shell 需要转换用户输入的内容以匹配其自己的规则,但我很难用语言描述多个反斜杠到底发生了什么。 argv
execve()
\\\n
答案1
strace
以 C 字符串语法显示字符串,其中单个反斜杠显示为\\
,换行符显示为\n
,依此类推。
传递给的是函数在给定C 源代码中作为参数打印的字符串文字时将打印的execve
内容。puts
strace
答案2
shell 对用双引号分隔的字符串几乎没有做任何事情,因为唯一适用的转义是将参数传递给调用的程序之前取消转义\\
to \
(适用于双引号字符串的少数转义规则之一) ,并且对于用单引号分隔的字符串没有任何内容。
strace
另一方面,试图写出一些看起来大致像 C 源代码的东西,尽管这并不完全是 C 语言函数调用的样子,重新转义任何\
s。
所以:
- 在
% strace -e execve printf '!\n' execve("/usr/bin/printf", ["printf", "!\\n"], [/* 35 vars */]) = 0 ! +++ 以 0 退出 +++ %
strace
传递给以及随后传递给 的参数printf
正是三个字符长的字符串!\n
。strace
将其打印为 C 语言字符串常量,其中\
字符串内部加倍,产生".\\n"
.当然,它printf
被解释为换行。\n
- 在
% strace -e execve printf '!\\n' execve("/usr/bin/printf", ["printf", "!\\\\\\n"], [/* 35 vars */]) = 0 !\ +++ 以 0 退出 +++ %
完全相同的情况正在发生,只是\
C 语言字符串中有更多的 s 需要加倍,并且printf
正在识别\\
后面的\n
。 - 在
% strace -e execve printf '!\\\\n' execve("/usr/bin/printf", ["printf", "!\\\\\\\\n"], [/* 35 vars */]) = 0 !\\n+++ 以 0 +++ 退出 %
发生的情况完全相同,只是\
C 语言字符串中有更多 s 需要加倍,并且printf
正在识别\\
后跟\\
后跟n
。 - 在
% strace -e execve printf \!"\\\\n" execve("/usr/bin/printf", ["printf", "!\\\\n"], [/* 35 vars */]) = 0 !\n+++ 以 0 +++ 退出 %
shell 正在减少参数,!\\n
因为 shell 语言中双引号单词的转义规则会减少\\
和\!
引用以防止识别历史扩展字符; C语言字符串是这样的"!\\\\n"
;并printf
正在看到\\
后面跟着n
。 - 在
% strace -e execve printf \!$'\007'"\\\\n" execve("/usr/bin/printf", ["printf", "!\7\\\\n"], [/* 35 vars */]) = 0 !\n+++ 以 0 +++ 退出 %
发生的情况大致相同,只是␇
字符的 C 语言字符串转义形式看起来与 shell 的完全不同,后者使用第三种引用形式。
答案3
正如其他人提到的,strace
打印系统调用参数的引用方式与 C 中特殊字符的引用方式相同。换行符表示为\n
,文字反斜杠表示为\\
、 a 等。(手册页)
字符指针被取消引用并打印为 C 字符串。字符串中的非打印字符通常由普通的 C 转义码表示。
set -x
查看 shell 发送给正在运行的命令的内容可能更容易。 Bash 将 xtrace 输出放在单引号中,反斜杠在其中不起作用。
在你的第一个例子中:
$ set -x
$ printf "\\\n" > /dev/null
+ printf '\\n'
第一个反斜杠转义第二个反斜杠,产生一个单一的\
.第三个反斜杠不会转义任何内容,因为它后面跟着一个不需要转义的字母,所以它是按字面意思理解的。这封信也是按字面意思理解的,所以我们得到\\n
。 C 引用,反斜杠加倍。
双引号内的反斜杠转义的字符标准中明确列出,它们是美元符号$
、反引号`
、双引号"
、反斜杠本身、\
和换行符。
由于历史扩展,感叹号是特殊的,但如果它被转义,它前面的反斜杠是不是在 Bash 中删除。然而,如果启用了历史扩展,Zsh 会删除它。
答案4
shell 确实减少了反斜杠的数量(在一种情况下)。但在 execve“C-quoting”中表示为双反斜杠。
用双引号括起来(破折号):
双引号 将字符括在双引号内可保留除美元符号 ($)、反引号 (`) 和反斜杠 (\) 之外的所有字符的字面含义。双引号内的反斜杠在历史上很奇怪,并且仅用于引用以下字符:
$ ` " \ <newline>。
否则它仍然是原义的。
所以,在这一行中:
$ strace -e execve printf "\\\n"
shell 稍微改变了参数,这就是 strace 接收到的:
$ strace -e execve printf "\ \n" # Space added for emphasis, not real.
只有第一个反斜杠才会引用下一个反斜杠。然后,execve 的引用机制,其中字符串表示为“C-quoted”字符串,将使使用的反斜杠数量加倍,两个变成四个:
$ execve("/usr/bin/printf", ["printf", "\\\\n"], [/* 42 vars */]) = 0
这就是所看到的。
用单引号括起来(man dash):
单引号
将字符括在单引号中可保留所有字符的字面含义(单引号除外,因此无法将单引号放入单引号字符串中)。
因此,strace 收到类似以下字符串的内容:
strace -e execve printf '\ \ \ \ n' #space(s) added for emphasis.
当 execve 进行“C 引用”时,四 (4) 个反斜杠变为八 (8):
execve("/usr/bin/printf", ["printf", "\\\\\\\\n"], [/* 42 vars */]) = 0
这与您的示例中看到的相匹配。