Shell Quirk: Assignment From a Heredoc

Saturday, June 10, 2017.

I have a fetish for fascination with POSIX shell corner cases. It all started a decade ago with a segfault: a certain while read loop ran fine on every Unix except AIX. We were stumped, and I was hooked.

Here's a new find. What will the following POSIX shell program print?

#!/bin/sh
paths=`tr '\n' ':' | sed -e 's/:$//'`<<EOPATHS
/foo
/bar
/baz
EOPATHS
echo "$paths"

If you said /foo:/bar:/baz, you're right...that is, if you're on Linux and /bin/sh is provided by dash.

If you're on MacOS [1] or FreeBSD instead, this same script will wait for input and print nothing. This is probably the behavior on all BSD derivatives, and it's likely the correct behavior too, since the BSDs are usually right about these things.

Correct or not, the dash behavior is a bit more useful. It also points to a fundamental difference in the way here-documents work: dash interprets the heredoc before anything else on the line. When the assignment is interpreted next, stdin already has the contents of the heredoc. I'm not even sure what the other POSIX shells do. Is the heredoc interpreted after the assignment? Where does it even go?

Fortunately there's an easy portable alternative: wrap the whole thing in backquotes.

#!/bin/sh
paths=`tr '\n' ':' | sed -e 's/:$//'<<EOPATHS
/foo
/bar
/baz
EOPATHS`
echo "$paths"

[1] Note that on recent MacOS versions, /bin/sh is actually bash in POSIX mode. Don't believe me? Run /bin/sh --help and /bin/sh -c 'echo $POSIXLY_CORRECT'.

Posted by Alan on Saturday, June 10, 2017.