为什么从 Makefile 调用时 printf 的行为不同?

Why does printf behave differently when called from a Makefile?

printf程序可用于打印二进制数据,例如:

$ printf '%b' '\xff\xff'
��

如果我把它单独放在一个 Makefile 中,它的工作原理是一样的:

all:
    printf '%b' '\xff\xff'
$ make
printf '%b' '\xff\xff'
��

但是,如果我想在 Makefile 中的同一个 shell 调用上做任何其他事情,例如将它重定向到一个文件,或者只是在之后打印其他东西,那么尽管 Make 打印的命令没有改变(表明这不是转义问题),但输出变为反斜杠后跟“x”后跟双“f”,两次:

all:
    printf '%b' '\xff\xff'; printf 'wtf?\n'
make
printf '%b' '\xff\xff'; printf 'wtf?\n'
\xff\xffwtf?

这是怎么回事?为什么一行中的两个 printf 与单个 printf 的行为不同?

@chepner 在他们的评论中走在了正确的轨道上,但细节不太正确:

This is wild speculation, but I suspect there is some sort of optimization being applied by make that causes the first example, as a simple command, to be executing a third option, the actual binary printf (found in /usr/bin, perhaps), rather than a shell. In your second example, the ; forces make to use a shell to execute the shell command line.

Make 始终使用 /bin/sh 作为其 shell,无论用户使用什么作为其 shell。在某些系统上,/bin/sh 是 bash(它有一个内置的 printf),而在某些系统上 /bin/sh 是不同的东西(通常是 dash,它是一个轻量级的, POSIX-符合 shell) 可能没有内置 shell。

在您的系统上,/bin/sh 是 bash。但是,当你有一个不需要 shell 的“简单命令”时(也就是说,make 本身有足够的简单引用智能来理解你的命令)那么为了更有效,make 将直接调用该命令而不是运行宁 shell.

这就是这里发生的事情:当您 运行 简单命令(没有 ;)时,make 将直接调用该命令并且 运行 /usr/bin/printf。当您 运行 更复杂的命令(包括 ;)时,make 将直接放弃 运行ning 命令并调用您的 shell... 即 bash,它使用 bash 的内置 printf.

基本上,您的脚本不符合 POSIX 标准(POSIX 标准中没有 %b),因此它的作用没有明确定义。如果您希望始终使用 SAME 行为,则应使用 /usr/bin/printf 强制始终使用该行为。强制 make 总是 运行 a shell 并且从不使用它的快速路径是非常棘手的;您需要在每个命令中包含一个特殊字符,例如尾随 ;