Search code examples
macosshellmakefile

The result of makefile execution is different from shell execution


my Makefile:

foo:
    echo -n "d41d8cd98f00b204e9800998ecf8427e" | openssl dgst -md5 -hmac "1@123456"

when I run: make foo, the output is different from the result of running the same command directly on the shell.(mac m2 arm64)

MacBook-Air:tmp $ make foo
echo -n "d41d8cd98f00b204e9800998ecf8427e" | openssl dgst -md5 -hmac "1@123456"
b6977416b3597483a9e416f4c04a1dcd
MacBook-Air:tmp$ echo -n "d41d8cd98f00b204e9800998ecf8427e" | openssl dgst -md5 -hmac "1@123456"
44ac6a36a27b1e5352a14e803929516f

But when I execute the same operation in an amd64 Ubuntu environment, the results are consistent.

tmp$ make foo
echo -n "d41d8cd98f00b204e9800998ecf8427e" | openssl dgst -md5 -hmac "1@123456"
MD5(stdin)= 44ac6a36a27b1e5352a14e803929516f
echo -n "d41d8cd98f00b204e9800998ecf8427e" | openssl dgst -md5 -hmac "1@123456"
MD5(stdin)= 44ac6a36a27b1e5352a14e803929516f

I am grateful for your guidance.


Solution

  • The echo command is built into the shell, so its behavior depends upon that shell, which is different between platforms. And make by default runs /bin/sh rather than whatever $SHELL is set to in the environment.

    If you remove the openssl command you can see the behavior difference clearly - the sh echo on macOS doesn't understand -n as an option, but just includes it in the output¹. You can reproduce the behavior like so:

     $ /bin/sh -c 'echo -n d41d8cd98f00b204e9800998ecf8427e'
     -n d41d8cd98f00b204e9800998ecf8427e
    

    (A complicating factor is that make tries to be smart about when it needs the shell and when it can get away with just exec()ing the command directly - so a plain echo with no pipe may do the latter and wind up running /bin/echo, which even on a Mac understands -n just fine. But any complications to the command will result in it being run by the shell.)

    You could set SHELL inside the Makefile, but that introduces its own set of portability problems; if you set it to /bin/zsh, for instance, the Makefile won't run on systems that don't have zsh installed, or have it in installed in /usr/bin or /usr/local/bin.

    I would change the echo -n to printf; that way it's consistent across shells, and printf already doesn't append a newline. In fact, it's probably a good idea to just forget echo exists and always use printf instead (just with a \n on the end when you do want the newline).

    But if you'd rather set SHELL, I would do so with something like this:

    SHELL := $(shell echo "$$SHELL")
    

    That tells make to use whatever the environment variable is set to, which will normally be what the calling shell is.

    ¹ The Bash 3.2 that ships with macOS changes its echo behavior when invoked as sh. Newer versions have stopped doing that, even with POSIXLY_CORRECT set, because while the POSIX standard specifies that echo shouldn't recognize -n, it also straight up admits echo is non-portable and says that it should be avoided except in unambiguous cases – i.e. you want to echo things literally and add a newline and there are no arguments that look like options or backslash escapes.