Usage and Best Practices of Linux Shebang (#!)

In many script files, we can see the following code on the <span>first line</span>:

#!/bin/bash
或
#!/usr/bin/env python

Carefully observe that the first line of the script file starts with the characters <span>#!</span>.

This character sequence consisting of <span>hash</span> and <span>exclamation mark</span> is called <span>Shebang</span> (also known as <span>Hashbang</span>).

Its purpose is to tell the system which <span>interpreter</span> should be used to execute this script file.

<span>Basic syntax:</span>

#!/path/to/interpreter [optional-arg]
  • <span>#!</span>: Fixed starting symbol. For portability, there should not be any characters (including spaces) before <span>#!</span>.
  • <span>/path/to/interpreter</span>: The absolute path of the interpreter program. For example, /bin/bash, /usr/bin/python3, /bin/sh, etc.
  • <span>[optional-arg]</span>: An optional argument. It is usually not commonly used, but a classic example is <span>#!/bin/sh -ex</span>, where <span>-ex</span> enables detailed execution logging for shell scripts.

<span>Key Point</span>: Must be located on the <span>first line</span> of the script file. Based on my tests, to make the <span>-ex</span> option effective in <span>#!/usr/bin/env -S bash -ex</span>, that line of code must be on the first line; even an empty line will not work. Therefore, there is no need to innovate here; just write it honestly on the first line.

1. Examples of Shebang Executing Scripts

Taking <span>bash</span> as an example, on my computer:

# Actual path of bash
$ which bash
/usr/bin/bash

Save the following code in <span>test.sh</span>:

#!/usr/bin/bash -ex
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

Grant execution permission to the script:

chmod 755 ./test.sh
# or
chmod +x ./test.sh

Execute the script:

$ ./test.sh
+ radius=2.5
++ awk -v r=2.5 'BEGIN {printf "%.3f", 3.14159 * r * r}'
+ result=19.635
+ echo 'area: 19.635'
area: 19.635

If the <span>Shebang</span> mechanism is not used, you have to specify the interpreter yourself:

bash ./test.sh

Now let’s change the content of <span>test.sh</span>, adding a space before <span>#!</span>:

 #!/usr/bin/bash -ex
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"
$ ./test.sh
area: 19.635

# The -ex option seems to be ignored!!! So do not add any characters before #!.

This is not that the <span>-ex</span> option is ignored, but rather that the operating system did not recognize the existence of the <span>Shebang</span>, and the system used the default shell (like <span>/bin/sh</span>) to execute it.

I tested this on <span>macOS</span> as follows:

 #!/usr/bin/bash -ex
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

# New code
sleep 30

Test it as follows:

# Run in the background
$ ./test.sh &amp;

$ ps -l
UID   PID  PPID      TIME CMD
501 14016 14015    0:03.40 -zsh
501 62761 14016    0:00.03 sh ./test.sh

As shown, the script is executed using <span>sh</span>.

Even if the default shell is <span>bash</span>, it is unrelated to the <span>Shebang</span> specified here; otherwise, the <span>-ex</span> option would definitely take effect.

2. /usr/bin/env

In the above example, we hard-coded the path of the <span>bash</span> interpreter. This makes our code unable to execute when moved to another machine with a different path.

$ which bash
/opt/homebrew/bin/bash

As shown, the path of <span>bash</span> on my other computer is different.

$ ./test.sh

./test.sh: bad interpreter: /usr/bin/bash: no such file or directory
# Error message indicates that the bash interpreter cannot be found.

There are two solutions: one is to modify the path of the bash interpreter, and the other is to use <span>/usr/bin/env</span>.

#!/usr/bin/env bash
set -ex
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

Executing it again works correctly, as shown:

$ ./test.sh
+ radius=2.5
++ awk -v r=2.5 'BEGIN {printf "%.3f", 3.14159 * r * r}'
+ result=19.635
+ echo 'area: 19.635'
area: 19.635

<span>#!/usr/bin/env bash</span> means: “Please find the program named <span>bash</span> from the <span>PATH</span> environment variable and use it to execute this script”.

Readers may have noticed that the above <span>#!/usr/bin/env bash</span> does not specify the <span>-ex</span> option anymore, but moves it into the script file as <span>set -ex</span>.

Of course, you can specify the <span>env</span> with the <span>-S</span> option to pass parameters:

#!/usr/bin/env -S bash -ex
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

It produces the same result as above:

$ ./test.sh
+ radius=2.5
++ awk -v r=2.5 'BEGIN {printf "%.3f", 3.14159 * r * r}'
+ result=19.635
+ echo 'area: 19.635'
area: 19.635

Here is an actual example in <span>Python</span>:

#!/usr/bin/env python
# coding: utf-8

import sys
print(sys.path)

It specifies both the <span>Shebang</span> and the encoding of the Python source code, see PEP 263 – Defining Python Source Code Encodings[1].

3. Summary

In summary, <span>Shebang</span> has the following considerations:

  • <span>Must be the first line</span>: The Shebang must be located on the first line of the file. There should not be any spaces or characters before <span>#!</span>.
  • <span>Requires executable permission</span>: To allow the script to run directly like a program (<span>./script</span>), you must give the script file executable permission using the <span>chmod +x script.sh</span> command.

<span>Shebang</span> is a simple yet powerful mechanism that allows text script files written in various languages to be conveniently launched like <span>binary executable files</span>. It automates and standardizes script execution by associating script files with the correct interpreter.

Finally, using <span>#!/usr/bin/env</span> is the best practice for writing portable scripts.

References

[1]

PEP 263 – Defining Python Source Code Encodings: https://peps.python.org/pep-0263/

Leave a Comment