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

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

在 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

运行此文件会产生:

$ make without-pipe 
foo

$ make with-bash-and-pipe 
foo

$ make with-pipe 
\x66\x6f\x6f

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

答案1

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


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

1. Make 有点智能

它似乎make确定是否需要 shell。我做到了

strace -f -e execve make without-pipe

输出部分为:

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

因此在这种情况下只需探测(根据)make的可能位置直到找到工具。printf$PATH

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

strace -f -e execve make with-pipe

收益率(以及其他线路)

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

该工具非常智能,可以告诉您想要运行管道。如果您想要运行管道,则将使用 shell。shell 是sh

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

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

  • printf是 bash 中的内置命令(用 确认type -a printf),因此make with-bash-and-pipe使用 bash 中的内置命令。

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

3.printf不需要理解\x66

POSIX 规范printf说:

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

[…]

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

但不适用于printfsh 中的内置命令。


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

您可以通过指定其完整路径来确保printf您运行的不是内置命令:

/usr/bin/printf '\x66\x6f\x6f' | cat

然而这需要你事先知道路径。要解决这个问题,你可以使用env

env printf '\x66\x6f\x6f' | cat

env将运行在 中找到的可执行文件$PATH。您可以期待env它的存在,因为它是 POSIX 所要求的。

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

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

例子:

printf '\146\157\157'

这种方法在这三种情况下都有效。

相关内容