Search code examples
linuxshellmakefileescaping

Output of printf '\x41\n' | cat in Makefile is different than the output in shell


I am using GNU Make on Linux. Here is my Makefile.

foo:
    printf '\x41\n'

bar:
    printf '\x41\n' | cat

Here is the output:

$ make foo
printf '\x41\n'
A
$ make bar
printf '\x41\n' | cat
\x41

The shell being used is Dash:

# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Sep 12 04:41 /bin/sh -> dash

Why does the output become different when I pipe through cat?

Strangely, dash itself has a different behavior when I execute the commands directly on it.

$ printf '\x41\n'
\x41
$ printf '\x41\n' | cat
\x41

What is going on here? Why is the output of the commands in Makefile inconsistent with that in Dash?


Solution

  • The reason you see different behavior is that the form \xNN is not defined in the POSIX standard for printf. The standard only defines the behavior for special characters specified in octal, using \oNNN. It doesn't support the hex form.

    The reason for seeing different behavior is that in the simple case:

    foo:
            printf '\x41\n'
    

    make doesn't need to invoke a shell: it can run the command directly which invokes the /usr/bin/printf program on your system. That program has extra features, in addition to what POSIX requires, and it can handle \xNN characters.

    In the more complicated case:

    bar:
            printf '\x41\n' | cat
    

    This requires make to invoke the shell, because make can't handle pipelines etc. When you invoke the shell it's using the shell's built-in printf not the separate program /usr/bin/printf. Since your shell is dash, which only provides POSIX-conforming behaviors (for the most part), its printf built-in doesn't handle non-standard \xNN characters.

    Short answer: stick with behaviors defined by POSIX and it will work all the time.