The first line of a shell script – beginning #! – is called a shebang and typically determines which interpreter parses the script’s contents. This is not always the case and on occasion it may be helpful to know which interpreter is being used.
Paste the following at a command prompt to create a script that demonstrates this:
cat << 'EOF' > "${HOME}/which-interpreter.sh"
#!/bin/bash
ps -p "$$" | tail -1 | awk '{print $4}'
EOF
This script simply displays the interpreter that parses it, but before running the script let’s look at what the line ps -p "$$" | tail -1 | awk '{print $4}' does.
The ps command with the -p flag outputs information about the process matching the process ID (PID). The $$ represents the PID of the script. The output of ps -p "$$" is:
PID TTY TIME CMD 3740 ttys000 0:00.01 /bin/bash /Users/steve/which-interpreter.sh
This output is piped to tail to remove the header:
3740 ttys000 0:00.01 /bin/bash /Users/steve/which-interpreter.sh
The CMD portion of the output shows the command that launched the script: /bin/bash and the parameter passed to it /Users/steve/which-interpreter.sh. Only the command is of interest so the output is piped to awk to print the fourth field:
/bin/bash
Make the script executable:
chmod +x "${HOME}/which-interpreter.sh"
Now let’s run the script:
"${HOME}/which-interpreter.sh"
/bin/bash
It doesn’t matter if the shell being used to run the script is a different version of bash or even another shell like zsh. The shebang will still be honoured and the script parsed by the /bin/bash interpreter unless another interpreter is explicitly used to execute the script at the command prompt.
Here are some examples:
NOTE: I have two bash interpeters on my system. The one shipped with macOS: /bin/bash and the one installed via Homebrew: /usr/local/bin/bash. The latter is a symbolic link to /usr/local/Cellar/bash/5.0.16/bin/bash.
/usr/local/bin/bash "${HOME}/which-interpreter.sh"
/usr/local/bin/bash
/bin/zsh "${HOME}/which-interpreter.sh"
/bin/zsh
/usr/bin/env bash "${HOME}/which-interpreter.sh"
bash
This last example only shows that the script is being parsed by a bash interpeter, but not which one. It could be /bin/bash or /usr/local/bin/bash or another bash interpreter.
To see which interpreter is being used, overwrite the existing script to include the which command:
cat << 'EOF' > "${HOME}/which-interpreter.sh"
#!/bin/bash
which $(ps -p "$$" | tail -1 | awk '{print $4}')
EOF
Now run the amended script:
/usr/bin/env bash "${HOME}/which-interpreter.sh"
/usr/local/bin/bash
Using /usr/bin/env bash from the command prompt or as part of a shebang instructs the script to use a bash interpreter to parse its contents, but doesn’t explicitly state which one. That is determined by the user’s search path which is stored in the PATH environmental variable:
echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
The script is parsed by the first instance of bash found in the search path. In my case this is /usr/local/bin/bash, not /bin/bash.
Overwrite the existing script changing the shebang to #!/usr/bin/env bash:
cat << 'EOF' > "${HOME}/which-interpreter.sh"
#!/usr/bin/env bash
which $(ps -p "$$" | tail -1 | awk '{print $4}')
EOF
Run the amended script:
"${HOME}/which-interpreter.sh"
/usr/local/bin/bash
Care should be taken when using launchd to execute scripts with this type of shebang as launchd has a default search path limited to /usr/bin:/bin:/usr/sbin:/sbin. Consequently, if this script were run by launchd it would be parsed by the only available bash interpeter in that search path: /bin/bash.