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
.