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 printf
s in one line behave differently than a single printf
?
CodePudding user response:
@chepner is on the right track in their comment but the details are not quite right.
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.