Why does make ignore escape sequence if the output is piped

4

In a Makefile, I want to print a byte represented as a hexadecimal number and pass it to STDIN of another program. For some reason, it doesn't work:

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

Running this file produces:

$ make without-pipe 
foo

$ make with-bash-and-pipe 
foo

$ make with-pipe 
\x66\x6f\x6f

What feature of make am I missing and what is the proper way to make the last target produce the same output. The one with-bash-and-pipe is a sort of workaround.

Sergei Morozov

Posted 2019-05-04T21:02:12.080

Reputation: 143

Answers

2

Note: my testbed is Ubuntu 18.04.2 LTS.


Few steps are required to understand the behavior.

1. Make is somewhat smart

It looks like make determines if a shell is required. I did

strace -f -e execve make without-pipe

and the part of the output was:

[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

So in this case make just probes possible locations of printf (according to $PATH) until the tool is found.

The behavior is different in the last case. This command

strace -f -e execve make with-pipe

yields (among other lines)

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

The tool is smart enough to tell you want to run a pipe. If you do, a shell is used. The shell is sh.

2. The three cases use different printf implementations

  • As shown above, make without-pipe uses printf executable available in the OS.

  • printf is a builtin in bash (confirm with type -a printf), so make with-bash-and-pipe uses the builtin from bash.

  • printf is also a builtin in sh (confirm with (unset PATH; printf 'printf works\n'; ls); if it wasn't a builtin, sh couldn't find the executable like it cannot find ls), so make with-pipe uses the builtin from sh.

3. printf is not required to understand \x66 and such

POSIX specification of printf says:

The format operand shall be used as the format string described in XBD File Format Notation with the following exceptions:

[…]

Neither of the two linked documents says \xHH and such must be special. I'm not sure if the specification explicitly allows them to be special, but the fact is they are special for some printf-s in the question.

But not for the printf builtin in sh.


what is the proper way to make the last target produce the same output? The one with-bash-and-pipe is a sort of workaround.

You can make sure printf you run is not a builtin by specifying its full path:

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

however this requires you to know the path beforehand. To fix this issue you can use env:

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

env will run the executable found in $PATH. You can expect env to be present because it's required by POSIX.

On the other hand in general you can't be sure any printf executable (builtin or not) does support \xHH in the first place. Therefore the real fix is to rewrite your format string so it's properly understood by all POSIX-compliant implementations of printf. This seems useful:

In addition to the escape sequences shown in XBD File Format Notation ( \\, \a, \b, \f, \n, \r, \t, \v ), \ddd, where ddd is a one, two, or three-digit octal number, shall be written as a byte with the numeric value specified by the octal number.

Example:

printf '\146\157\157'

This approach works in all three cases.

Kamil Maciorowski

Posted 2019-05-04T21:02:12.080

Reputation: 38 429

Thank you for the comprehensive explanation.

The only remaining question is, is there a portable way to print a byte from the hexadecimal (or decimal) representation which doesn't require a lot of extra dependencies. – Sergei Morozov – 2019-05-05T01:42:12.157