POSIX sh and shebangs

Posted on January 17, 2021

As I keep moving towards POSIX, I’m on the process of migrating all my Bash scripts to POSIX sh.

As I dropped [[, arrays and other Bashisms, I was left staring at the first line of every script, wondering what to do: what is the POSIX sh equivalent of #!/usr/bin/env bash? I already knew that POSIX says nothing about shebangs, and that the portable way to call a POSIX sh script is sh script.sh, but I didn’t know what to do with that first line.

What I had previously was:

1
2
3
#!/usr/bin/env bash
set -Eeuo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"

Obviously, the $BASH_SOURCE would be gone, and I would have to adapt some of my scripts to not rely on the script location. The -E and -o pipefail options were also gone, and would be replaced by nothing.

I converted all of them to:

1
#!/bin/sh -eu

I moved the -eu options to the shebang line itself, striving for conciseness. But as I changed callers from ./script.sh to sh script.sh, things started to fail. Some tests that should fail reported errors, but didn’t return 1.

My first reaction was to revert back to ./script.sh, but the POSIX bug I caught is a strong strain, and when I went back to it, I figured that the callers were missing some flags. Specifically, sh -eu script.sh.

Then it clicked: when running with sh script.sh, the shebang line with the sh options is ignored, as it is a comment!

Which means that the shebang most friendly with POSIX is:

1
2
#!/bin/sh
set -eu
  1. when running via ./script.sh, if the system has an executable at /bin/sh, it will be used to run the script;
  2. when running via sh script.sh, the sh options aren’t ignored as previously.

TIL.