'Why does printf behave differently when called from a Makefile?

The printf program can be used to print binary data, e.g.:

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

If I put this in a Makefile on its own, it works the same:

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

However, if I want to do anything else on the same shell invocation in the Makefile, for example to redirect it to a file, or just printing something else afterwards, then although the command printed by Make doesn't change (suggesting it's not an escaping issue), but the output changes to a backslash followed by an "x" followed by a double "f", twice:

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

What is going on here? Why do the two printfs in one line behave differently than a single printf?



Solution 1:[1]

@chepner is on the right track in their comment but the details are not quite right:

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 always uses /bin/sh as its shell, regardless of what the user is using as their shell. On some systems, /bin/sh is bash (which has a builtin printf) and on some systems /bin/sh is something different (typically dash which is a lightweight, POSIX-conforming shell) which probably doesn't have a shell built-in.

On your system, /bin/sh is bash. But, when you have a "simple command" that doesn't require a shell (that is, make itself has enough trivial quoting smarts to understand your command) then to be more efficient make will invoke that command directly rather than running the shell.

That's what's happening here: when you run the simple command (no ;) make will invoke the command directly and run /usr/bin/printf. When you run the more complex command (including a ;) make will give up running the command directly and invoke your shell... which is bash, which uses bash's built-in printf.

Basically, your script is not POSIX-conforming (there is no %b in the POSIX standard) and so what it does is not well-defined. If you want the SAME behavior always you should use /usr/bin/printf to force that always to be used. Forcing make to always run a shell and never use its fast path is much trickier; you'll need to include a special character like a trailing ; in each command.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cactus