POSIX shell and shebangs

If you’re writing scripts in the POSIX shell command language for portability reasons, it’s important to be aware that POSIX 1 does not define behavior for shebangs or establish a standardized path where systems must place their POSIX-compliant shell. As a result, the use of shebangs such as #!/bin/sh and #!/usr/bin/sh, which assume the system’s POSIX-compliant shell is available on /bin/sh/ and /usr/bin/sh, respectively, may be misguided. I’ll clarify later why the use of shebangs is not necessarily a misplaced practice.

To address the lack of a standardized path to its shell, POSIX suggests using the POSIX utility getconf to determine the system’s default value for the environment variable PATH. The system-provided PATH contains search paths to POSIX utilities. If you do not use getconf, then you risk depending on a PATH that may have been modified by the user to include directories containing utilities that replace the POSIX ones.

On systems supporting shebangs, it is recommended to determine the POSIX-compliant shell’s pathname using getconf and inject the associated shebang into your shell scripts. Below is an example script from the POSIX sh documentation that is a useful starting point for a script of your own.

sh

#
# Installation time script to install correct POSIX shell pathname
#
# Get list of paths to check
#
Sifs=$IFS
Sifs_set=${IFS+y}
IFS=:
set -- $(getconf PATH)
if [ "$Sifs_set" = y ]
then
    IFS=$Sifs
else
    unset IFS
fi
#
# Check each path for 'sh'
#
for i
do
    if [ -x "${i}"/sh ]
    then
        Pshell=${i}/sh
    fi
done
#
# This is the list of scripts to update. They should be of the
# form '${name}.source' and will be transformed to '${name}'.
# Each script should begin:
#
# #!INSTALLSHELLPATH
#
scripts="a b c"
#
# Transform each script
#
for i in ${scripts}
do
    sed -e "s|INSTALLSHELLPATH|${Pshell}|" < ${i}.source > ${i}
done

Some programmers are aware of the pitfalls of shebangs but still opt to start their shell scripts with the shebang #!/bin/sh. The most common rationale I’ve encountered is that on most Linux systems, which are often the only systems these programmers care about, the POSIX-compliant shell is available on /bin/sh. Given the specific context, I find this justification practical enough to warrant the use of the shebang #!/bin/sh. However, if you are concerned about running your scripts on systems where the POSIX-compliant shell is not located at /bin/sh, using getconf becomes a more practical approach.2 For example, on an Oracle Solaris 11 system, the POSIX-compliant shell resides at /usr/xpg4/bin/sh instead of /bin/sh. 3 As long as you expect to only target Linux systems for the forseeable future, you’ll likely face minimal issues. Ultimately, the key to making the best decision lies in understanding the specific set of systems you are targeting to run your programs.

Footnotes


  1. Whenever I mention POSIX in this article, I’m talking about POSIX.1-2017 unless I specify otherwise. ↩︎

  2. Unfortunately, there is the possibility of getconf being set to an alias by a user. ↩︎

  3. I learned about the POSIX-compliant shell location on Oracle Solaris 11 from the documentation on customizing a user’s work environment↩︎