What exactly happens when we run a file starting with
#! (aka shebang), and why some people use
#! shebang is used to tell the kernel which interpreter should be used to run the commands present in the file.
When we run a file starting with
#!, the kernel opens the file and takes the contents written right after the
#! until the end of the line. For didactic purposes, let's consider it saves in a variable called
command the string starting after the shebang and ending in the end of line.
After this the kernel tries to run a command with the contents of the
command and giving as the first argument the filename of the file we're trying to execute.
Therefore, if you have an executable file called
myscript.sh with some shell commands and starting with
#!/bin/bash, when you run it, the kernel will execute
In the examples below you're going to see it very clearly.
Starting with the classic
#!/bin/bash echo "Hello World!"
Assuming this file has the executable permission, when you type this in the command line:
The kernel will notice the
#! in the very first line and then will get what's after it, in this case
/bin/bash. And then what is executed has the very same effect of this:
$ /bin/bash hello.sh
Let's use another example using
#!/bin/cat. The name of the file is
#!/bin/cat All the contents of this file will be printed in the screen when it's executed (including the '#!/bin/cat' in the first line).
- What's after the shebang:
- Name of the file:
Therefore this is what's executed:
See it by yourself:
$ ./shebangcat #!/bin/cat All the contents of this file will be printed in the screen when it's executed (including the '#!/bin/cat' in the first line).
Let's take another example to make it very clear that things are like I'm saying. The following file is called
#!/usr/bin/echo The contents of this file will *NOT* be printed when it's executed.
$ ./shebangecho ./shebangecho
The output was the name of the file because this is what was executed by the kernel
Another interesting thing, is that if we pass arguments when calling our script, such arguments will also be passed to the command executed by the kernel. As we can see in the following example called
#!/bin/ls The contents here doesn't matter.
Now, when we run it:
$ ./shebangls.sh ./shebangls.sh $ ./shebangls.sh -l -rwxr-xr-x 1 meleu meleu 41 Nov 28 14:42 ./shebangls.sh $ ./shebangls.sh notfound /bin/ls: cannot access 'notfound': No such file or directory ./shebangls.sh
You probably saw some scripts starting with
#!/usr/bin/env bash where you're used to see just
#!/bin/bash. The reason of this is to increase the portability of the script (even thought it's a debatable matter, as we're going to see below).
env command, if used with no arguments, prints a (big) list with all the environment's variables. But if
env is used followed by a command, it runs that command in another instance of the shell.
🤔 - OK, but how does that influence portability?!
When you use
#!/bin/bash you're clearly saying that
bash is in the
/bin/ directory. This seems to be the default in all Linux distributions, but there are other Unix flavors where it can possibly not happen (for example the
bash can be placed in the
/usr/bin/). In systems like that your script starting with
#!/bin/bash would cause a
bad interpreter: No such file or directory.
When you run
env bash, the
env will search for
bash in your
$PATH variable, and then run the first one it finds. Usually
bash is in
/bin/, but a user running your script on some other system can have it in
/usr/bin/ or even testing an alternative version in
So, in order to make the script have a greater reach and be used in environments other than Linux, some people recommend the use of the
🤔 - But wait! What guarantees that the
env will always be in the
There are no guarantees... 😇
The recomendation is based in what is commonly seen in the Unix systems. I see
/usr/bin/env being used in some modern projects (like RetroPie), but where it's specially useful is when you need to run a python or even a NodeJS script.
Let's take this NodeJS usage as an example. I want to call a NodeJS script just by calling the script's filename. Then I could do something like this:
#!/usr/bin/node console.log('Hello World from NodeJS');
The problem is that I usually install node via Node Version Manager, instead of using the the distribution's package manager. So, my
node is like this:
$ which node /home/meleu/.nvm/versions/node/v14.15.1/bin/node
By any means I want to put
#!/home/meleu/.nvm/versions/node/v14.15.1/bin/node in my script!
So, the solution here is to use
I strongly recommend you to never write neither run a shell script without a
As we said, the shebang tells to the kernel which interpreter is to be used to run the commands present in the file. If you run a script without specifying the interpreter, the shell will spawn another instance of itself and try to run the commands in the script. Which means that it will execute whatever commands found in the file, even if it was written for zsh, ksh, dash, fish, node, python, or whatever.
Summing up: Always start your scripts with a
#! shebang. Preferably with
- Here's an email from Dennis Ritchie in 1980, talking about these "magic characters".
- Node.js shebang