在 awk--Shell 扩展和引用删除中调用 bash 时字符 \ 如何导致不同的输出?

How character \ result in different outputs when to call bash in awk--Shell Expansions and Quote Removal?

用 printf 将十进制数 32 转换成它的十六进制值。

printf '%x\n'  32
20

在awk中调用上述bash的正确awk语句如下

awk 'BEGIN{system("printf '\''%x\n'\''  32")}'
20

我的问题与此无关。
对于 awk 中的以下调用 bash,\?
在哪里 字符 \ 丢失,为什么?

~$ awk 'BEGIN{system("printf '%x\n'  32")}'
20n~$ 

\n 没有被解析为换行符,\n 也没有被解析为 ,而是 n只是,为什么解析器丢失了它——\?
为了让我的问题更清楚,

awk 'BEGIN{system("printf '%xn'  32")}' 
awk 'BEGIN{system("printf '%x\n'  32")}'

为什么两个awk语句输出相同的字符串? 我们获得了更多关于 shell 扩展和引用删除的知识。

GNU awk manual

After the preceding expansions, all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed.

awk 'BEGIN{system("printf '%xn' 32")}' == awk 'BEGIN{system("printf '%x\n' 32")}'可以解释。

下面的怎么样? 如果多个字符 \ 多于一个 \

怎么办
awk 'BEGIN{system("printf '%x\n'  32")}'
0sh: 2: 32: not found

为什么不

awk 'BEGIN{system("printf '%x\n'  32")}'  ==  awk 'BEGIN{system("printf '%xn'  32")}'

更有趣的是,让我们在其中添加更多的\。

$ awk 'BEGIN{system("printf '%x\\n'  32")}'
0sh: 2: 32: not found

要在里面加上四五个\s

$ awk 'BEGIN{system("printf '%x\\n'  32")}'
20n$ awk 'BEGIN{system("printf '%x\\\n'  32")}'
20n$ 

这里最有趣的事情:

awk 'BEGIN{system("printf '%x\\\n'  32")}'
20$ 

字符 n 丢失且没有换行符。

你应该这样写:

$ awk 'BEGIN { system("printf \"%x\n\" 32") }'
20
$ awk 'BEGIN { system("printf '\''%x\n'\'' 32") }'
20
$ awk 'BEGIN { system("printf '\''%x\n'\'' 32") }'
20
$ awk $'BEGIN { system("printf \'%x\n\' 32") }'
20
$

在Bash中,你的'BEGIN{system("printf '%x\n' 32")}'实际上是3个分开的字符串连接成一个:

1)  'BEGIN{system("printf '
2)  %x\n                     <-- this will become %xn
3)  ' 32")}'

所以你的命令实际上是 awk 'BEGIN{system("printf %xn 32")}':

$ printf '%s\n' 'BEGIN{system("printf '%x\n' 32")}'
BEGIN{system("printf %xn 32")}
$ awk 'BEGIN{system("printf '%x\n' 32")}'
20n$ awk 'BEGIN{system("printf %xn 32")}'
20n$

如果手头的命令不仅仅是一个示例,那么值得听取 shellter 的有用建议,以使用 awkbuilt -in printf 函数 - 无需使用 external printf 实用程序调用 system()
awk 'BEGIN{ printf "%x\n", 32 }' 工作正常。

总的来说,你有3层引用要处理,顺序是:

  • 首先,当前的 shell (bash) 解释命令的标记 - 引用的和未引用的。

  • awk 然后查看此解释的结果并对嵌入的双引号 printf 命令字符串执行自己的解释。

  • 结果传递给 system(),它调用 /bin/sh,其中字符串再次由 shell 解释(sh,在这种情况)。

您的原始命令:

awk 是您的第一个命令附带的;重要的是 shell 的 (Bash's) 字符串引号:

  • POSIX-like shells such as Bash 允许通过放置任意混合的未引号、单引号和双引号(内插, 扩展) 字符串直接相邻。

  • 单引号字符串 - '...' - 支持嵌套.

  • 因此,'BEGIN{system("printf '%x\n' 32")}'分解为:

    • 'BEGIN{system("printf '%x\n' - 单引号 shell 字符串,其内容按原样使用。

    • %x\n未加引号 shell 的字符串受 shell expansions:

      • %x 按原样使用。
      • \-给字符加前缀是 字符单独形式的引用\<char> 告诉 shell <char>字面意思是,所以\n变成普通的n。只有 shell 元字符(如 | 通常具有特殊含义的字符)才需要这种形式的引用,并且由于 n 不是其中之一,因此 \nn 实际上是相同的,所以 %xn - 没有 \ - 会导致 same 文字。
    • ' 32")}',单引号 shell 字符串,其内容按原样使用。

  • 因此,在扩展之后,包括删除引号字符('\ 在这种情况下,一个称为 quote removal 的过程)和串联,shell 最终将以下文字传递给 awk
    BEGIN{system("printf %xn 32")}

  • 如您所见,awk 从未见过 \

    • 因为 Awk 在字符串 "printf %xn 32" 中找不到转义序列,它会将文字 printf %xn 32 传递给 system() 函数,后者会用指定的字符串调用 /bin/sh
    • 因此,shell 命令 printf %xn 32 被执行,打印 20n,没有尾随换行符。
      • 请注意,由于传递给 /bin/sh,此命令再次受到 shell 扩展的影响(与 bash 中的方式基本相同,除了进程替换和,可能是 Bash 特定的参数扩展),但在这种情况下它们不会导致任何变化。

您的跟进问题:

基于上述解释:

awk 'BEGIN{system("printf '%x\n' 32")}' 导致以下文字 awk 脚本:

BEGIN{system("printf %x\n  32")}

\n,从 shell 的角度来看,是 \ - 一个带引号的 \ 字符 - 后跟 n,结果是 文字 \n.

在这种情况下,控制字符转义序列 \nAwk 解释并在之前转换为 actual 换行符system() 函数将字符串传递给 /bin/sh

因此,/bin/sh 看到 两个 命令:

printf %x
32

printf %x 打印 0,因为缺少格式字符的参数。 %x 值默认为 032 本身不是有效命令,因此错误消息 sh: 2: 32: not found2 是行号)。


awk 'BEGIN{system("printf '%x\\n' 32")}'与前面的命令相同:

\\n,从 shell 的角度来看,是 \ - 带引号的 \ 字符 - 后跟 \n - 带引号的 n 字符 - 再次导致 literal \n.


至于awk 'BEGIN{system("printf '%x\\n' 32")}':

\\n 结果为文字 \n.

awk 反过来将其解释为 文字 \n.

/bin/sh 然后再次将 \n 解释为单独引用的 n 文字,有效地执行 printf %xn 32,产生 20n,没有尾随换行符.


至于awk 'BEGIN{system("printf '%x\\\n' 32")}':

awk 看到 \\n,它变成文字 \,后跟一个实际的换行符。 因此,awk 最终传递给 /bin/sh 的内容如下所示:

printf %x\
  32

上面包含一个 \ 转义的实际换行符,被 /bin/sh 解释为 单个 命令(这就是续行的工作原理在 shell) 中,所以它实际上与
相同 printf %x 32 并导致 20,没有尾随换行符。