Almost every Bash script starts with something like:
#!/bin/bash
or, these days, quite often:
#!/usr/bin/env bash
This special line is called a shebang. It tells the operating system which interpreter should be used to execute the script.
And despite looking very much like Bash code, #!/bin/bash is not a Bash command.
What is a shebang?
The name comes from the two characters at the beginning:
#!
They are followed by the path to the interpreter.
For example:
#!/bin/bash
means:
"Run this script using Bash."
The same mechanism can be used with other interpreters:
#!/usr/bin/env python3
#!/usr/bin/perl
#!/usr/bin/ruby
So the shebang is not specific to Bash. It is a way for an executable text file to tell the operating system which program should interpret it.
What happens when you run a script?
Suppose we have this file:
#!/bin/bash
echo "Hello, world!"
and make it executable:
chmod +x hello.sh
When we run:
./hello.sh
the operating system examines the beginning of the file, sees:
#! /bin/bash
and starts /bin/bash to interpret the script.
Conceptually, the result is similar to running:
/bin/bash ./hello.sh
The important difference is that with:
./hello.sh
the interpreter is determined by the shebang.
Why must the shebang be the first line?
Because the operating system only recognizes the interpreter declaration at the very beginning of the file.
This works:
#!/bin/bash
echo "Hello"
This does not:
# My Bash script
#!/bin/bash
echo "Hello"
In the second case, the operating system does not see #! as the first two characters of the file, so it cannot use the shebang to determine the interpreter.
If you want a comment at the beginning of your script, put it after the shebang:
#!/bin/bash
# My Bash script
echo "Hello"
./script.sh and bash script.sh are not the same thing
There is an important distinction between these two commands:
./script.sh
and:
bash script.sh
When you run:
./script.sh
the operating system uses the script's shebang to determine which interpreter to start.
When you run:
bash script.sh
you have already explicitly selected Bash. The shebang is no longer involved in choosing the interpreter.
This also means that:
sh script.sh
does not mean "run the script according to its shebang."
You explicitly told the system to run it with sh.
That distinction can become very important.
sh is not necessarily Bash
One of the most common shell-related mistakes is assuming that sh and bash are interchangeable.
They are not.
sh refers to the traditional/POSIX shell interface. On many modern Linux distributions, /bin/sh is actually a link to another shell implementation.
For example, on Debian and Ubuntu it commonly points to dash.
You can check this with:
ls -l /bin/sh
A Bash script might contain Bash-specific features such as:
if [[ "$name" == "Pavel" ]]; then
echo "Hello"
fi
or arrays:
servers=("web01" "web02" "web03")
These are Bash features and should not be assumed to work when the script is executed with sh.
Therefore, if your script is a Bash script, say so:
#!/usr/bin/env bash
and run it as a Bash script.
If you deliberately want a POSIX shell script, use:
#!/bin/sh
and write the script using POSIX shell syntax.
/bin/bash vs /usr/bin/env bash
You will commonly see two different forms of Bash shebang:
#!/bin/bash
and:
#!/usr/bin/env bash
They are similar, but not identical.
#!/bin/bash
This specifies the interpreter using an absolute path:
#!/bin/bash
The system will use exactly /bin/bash.
You can check whether Bash exists there with:
ls -l /bin/bash
#!/usr/bin/env bash
Here, the operating system starts env:
/usr/bin/env
and passes it the name:
bash
env then searches for bash using the current PATH.
You can see which Bash would be found with:
command -v bash
This approach can be useful on systems where Bash may be installed somewhere other than /bin/bash.
However, env is not magic. /usr/bin/env itself must exist, and bash must be available through PATH.
In a minimal container or restricted environment, either assumption can fail.
Why does the executable permission matter?
A shebang becomes relevant when you execute the script directly.
For example:
./hello.sh
requires the file to have the executable bit set.
You can check its permissions with:
ls -l hello.sh
and make it executable with:
chmod +x hello.sh
After that:
./hello.sh
can use the shebang.
But this:
bash hello.sh
does not require the script itself to be executable because you are executing bash, and Bash is reading the file as input.
This distinction is especially useful when troubleshooting scripts that work with:
bash script.sh
but fail with:
./script.sh
The shebang is not Bash syntax
If you open a Bash script and Bash reads it directly, the line:
#!/bin/bash
looks like a comment.
And from Bash's point of view, it essentially is one.
The special meaning comes from the operating system when the file is executed directly.
This is why:
bash script.sh
still works even though Bash does not need the shebang to know which interpreter it is already running under.
Don't put -e in the shebang
You may occasionally encounter:
#!/bin/bash -e
This attempts to start Bash with the -e option.
For normal scripts, it is much clearer and more portable to put shell options inside the script:
#!/usr/bin/env bash
set -e
Or, for a script where you deliberately want the commonly used strict-mode combination:
#!/usr/bin/env bash
set -euo pipefail
This also makes the script's behavior much easier to see and maintain.
A good modern Bash script
A simple modern Bash script might therefore look like this:
#!/usr/bin/env bash
set -euo pipefail
CONFIG_FILE="/etc/myapp/config.conf"
if [[ ! -f $CONFIG_FILE ]]; then
echo "Configuration file not found: $CONFIG_FILE" >&2
exit 1
fi
echo "Configuration found"
The first line answers:
Which interpreter should run this file?
The set line answers a different question:
How should that Bash interpreter behave?
Keeping these two things separate makes the script easier to understand.
The shebang can point to almost any interpreter
There is nothing special about Bash in this mechanism.
For example, a Python script can start with:
#!/usr/bin/env python3
A Perl script can use:
#!/usr/bin/env perl
And a Ruby script can use:
#!/usr/bin/env ruby
The important thing is that the selected program understands the rest of the file.
The operating system doesn't care whether the interpreter is Bash, Python, Perl, Ruby, or something else. It simply follows the interpreter specified by the shebang.
What happens with source?
There is another important case:
source script.sh
or:
. script.sh
Here the script is not started as a separate executable process.
Its commands are read and executed by the current shell.
Therefore, the shebang does not select a new interpreter in this situation.
This is particularly important for files intended to define environment variables, functions, aliases, or shell configuration.
If a file is intended to be sourced by Bash, it should still contain appropriate Bash syntax, but the shebang does not control what happens when the file is sourced.
A surprisingly common problem: Windows line endings
Sometimes a perfectly valid Bash script refuses to run and produces an error similar to:
/usr/bin/env: 'bash\r': No such file or directory
The strange \r is the clue.
The file probably uses Windows-style CRLF line endings instead of Unix/Linux LF line endings.
You can inspect the file with:
file script.sh
or:
cat -A script.sh
If necessary, convert the file to Unix line endings.
This problem is particularly common when scripts move between Windows and Linux, or when they are edited using tools configured for Windows line endings.
Another possible problem: UTF-8 BOM
Some editors can add a UTF-8 BOM (Byte Order Mark) at the beginning of a file.
That means the file may not literally begin with:
#!
even though it looks correct in an editor.
You can inspect the first few bytes with:
xxd -l 16 script.sh
A clean shebang should begin with the bytes corresponding to:
23 21
which represent:
#!
A BOM before those bytes can prevent the system from recognizing the shebang correctly.
Which shebang should you use?
For a Bash script, two common choices are:
#!/bin/bash
or:
#!/usr/bin/env bash
For a POSIX shell script:
#!/bin/sh
For Python:
#!/usr/bin/env python3
The important thing is to choose the interpreter that actually matches the language and features used by the script.
Don't write:
#!/bin/sh
just because sh is shorter if your script is full of Bash-specific features.
The operating system will happily start sh for you. It just won't necessarily be happy about what happens next.
Useful commands when troubleshooting a shebang
When a script behaves strangely, these commands can quickly reveal what's going on:
head -n 1 script.sh
Show the first line.
command -v bash
Show where Bash is found in PATH.
file script.sh
Check the file type and often its line-ending format.
xxd -l 16 script.sh
Inspect the first bytes, useful for detecting a BOM.
ls -l script.sh
Check the executable permission.
chmod +x script.sh
Make the script executable.
Then try:
./script.sh
Quick reference
| Script type | Typical shebang |
|---|---|
| Bash | #!/usr/bin/env bash |
| Bash with fixed path | #!/bin/bash |
| POSIX shell | #!/bin/sh |
| Python 3 | #!/usr/bin/env python3 |
| Perl | #!/usr/bin/env perl |
| Ruby | #!/usr/bin/env ruby |
The important part
The line:
#!/bin/bash
is not a Bash command.
It is a shebang, and its job is to tell the operating system which interpreter should be used when the file is executed directly.
Keep these three cases in mind:
./script.sh
Use the interpreter specified by the shebang.
bash script.sh
Explicitly use Bash; the shebang is not used to choose the interpreter.
source script.sh
Execute the file inside the current shell; the shebang does not start another interpreter.
Once you understand this distinction, the mysterious #!/bin/bash at the top of every script becomes considerably less mysterious.
And, as with many things in Unix, it turns out that the line that looks like a command isn't actually a command at all.