如果输出是通过管道传输的,为什么 make 会忽略转义序列

Ser*_*zov 5 pipe make escape-characters binary-files

在 Makefile 中,我想打印一个以十六进制数字表示的字节并将其传递给另一个程序的 STDIN。由于某种原因,它不起作用:

without-pipe:
    @printf '\x66\x6f\x6f'

with-bash-and-pipe:
    @/bin/bash -c "printf '\x66\x6f\x6f' | cat"

with-pipe:
    @printf '\x66\x6f\x6f' | cat
Run Code Online (Sandbox Code Playgroud)

运行该文件会产生:

$ make without-pipe 
foo

$ make with-bash-and-pipe 
foo

$ make with-pipe 
\x66\x6f\x6f
Run Code Online (Sandbox Code Playgroud)

我缺少什么功能make以及使最后一个目标产生相同输出的正确方法是什么。这with-bash-and-pipe是一种解决方法。

Kam*_*ski 2

注意:我的测试平台是 Ubuntu 18.04.2 LTS。

\n
\n

只需几个步骤即可理解该行为。

\n

1. Make有点聪明

\n

它看起来像是make确定是否需要外壳。我做到了

\n
strace -f -e execve make without-pipe\n
Run Code Online (Sandbox Code Playgroud)\n

输出的部分是:

\n
[pid 17526] execve("/usr/local/sbin/printf", ["printf", "\\\\x66\\\\x6f\\\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)\n[pid 17526] execve("/usr/local/bin/printf", ["printf", "\\\\x66\\\\x6f\\\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)\n[pid 17526] execve("/usr/sbin/printf", ["printf", "\\\\x66\\\\x6f\\\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)\n[pid 17526] execve("/usr/bin/printf", ["printf", "\\\\x66\\\\x6f\\\\x6f"], 0x5569b5a5b440 /* 67 vars */) = 0\n
Run Code Online (Sandbox Code Playgroud)\n

所以在这种情况下make只是探测printf(根据$PATH)的可能位置,直到找到该工具。

\n

最后一种情况的行为有所不同。这个命令

\n
strace -f -e execve make with-pipe\n
Run Code Online (Sandbox Code Playgroud)\n

收益率(以及其他行)

\n
[pid 17592] execve("/bin/sh", ["/bin/sh", "-c", "printf \'\\\\x66\\\\x6f\\\\x6f\' | cat"], 0x561465476440 /* 67 vars */) = 0\n
Run Code Online (Sandbox Code Playgroud)\n

该工具足够智能,可以告诉您想要运行管道。如果这样做,则会使用外壳。外壳是sh.

\n

2. 三种情况使用不同printf实现方式

\n
    \n
  • 如上图所示,make without-pipe使用printf操作系统中可用的可执行文件。

    \n
  • \n
  • printf是 bash 中的内置函数(用 确认type -a printf),所以make with-bash-and-pipe使用 bash 中的内置函数。

    \n
  • \n
  • printf也是 sh 中的内置函数(用 确认(unset PATH; printf \'printf works\\n\'; ls);如果它不是内置函数,则 sh 无法像无法找到一样找到可执行文件ls),因此make with-pipe使用 sh 中的内置函数。

    \n
  • \n
\n

3.printf不需要理解\\x66之类的

\n

POSIX 规范printf说:

\n
\n

格式操作数应用作 XBD文件格式表示法中描述的格式字符串但以下情况除外:

\n

[\xe2\x80\xa6]

\n
\n

两个链接的文件都没有说\\xHH这一定是特殊的。我不确定规范是否明确允许它们是特殊的,但事实是它们对于某些人来说是特殊的printf问题中的某些 -s 是特殊的。

\n

但不是为了printfsh 中的内置函数。

\n
\n
\n

使最后一个目标产生相同输出的正确方法是什么?唯一的那个with-bash-and-pipe是一种解决方法。

\n
\n

你可以确定printf您运行的不是内置程序:

\n
/usr/bin/printf \'\\x66\\x6f\\x6f\' | cat\n
Run Code Online (Sandbox Code Playgroud)\n

但这需要您事先了解路径。要解决此问题,您可以使用env

\n
env printf \'\\x66\\x6f\\x6f\' | cat\n
Run Code Online (Sandbox Code Playgroud)\n

env将运行 中找到的可执行文件$PATH。您可以env在场,因为 POSIX 要求这样做。

\n

另一方面,一般来说,您无法确定任何printf可执行文件(内置或非内置)\\xHH首先支持。因此,真正的解决方法是重写格式字符串,以便所有 POSIX 兼容的实现都能正确理解它printf。这似乎很有用:

\n
\n

除了 XBD 文件格式表示法 ( \\\\, \\a, \\b, \\f, \\n, \\r, \\t, \\v)中显示的转义序列\\ddd(其中ddd是一位、两位或三位八进制数)外,还应将其写入为字节,其数值由八进制数。

\n
\n

例子:

\n
printf \'\\146\\157\\157\'\n
Run Code Online (Sandbox Code Playgroud)\n

此方法适用于所有三种情况。

\n