为什么 strace 中反斜杠的数量会增加,而不是通过 bash 规则减少?

为什么 strace 中反斜杠的数量会增加,而不是通过 bash 规则减少?

这个问题与如何使用 printf 打印反斜杠后跟换行符?,其中 OP 尝试打印\\\n为单个反斜杠后跟换行符(不是文字\n)。

\\虽然根据 shell 规则,将扩展为\\nas 是有意义的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 需要转换用户输入的内容以匹配其自己的规则,但我很难用语言描述多个反斜杠到底发生了什么。 argvexecve()\\\n

答案1

strace以 C 字符串语法显示字符串,其中单个反斜杠显示为\\,换行符显示为\n,依此类推。

传递给的是函数在给定C 源代码中作为参数打印的字符串文字时将打印的execve内容。putsstrace

答案2

shell 对用双引号分隔的字符串几乎没有做任何事情,因为唯一适用的转义是将参数传递给调用的程序之前取消转义\\to \(适用于双引号字符串的少数转义规则之一) ,并且对于用单引号分隔的字符串没有任何内容。

strace另一方面,试图写出一些看起来大致像 C 源代码的东西,尽管这并不完全是 C 语言函数调用的样子,重新转义任何\s。

所以:

  • % strace -e execve printf '!\n'
    execve("/usr/bin/printf", ["printf", "!\\n"], [/* 35 vars */]) = 0
    +++ 以 0 退出 +++
    %
    strace传递给以及随后传递给 的参数printf正是三个字符长的字符串!\nstrace将其打印为 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

这与您的示例中看到的相匹配。

相关内容