614

In the header of a Bash script, what's the difference between those two statements:

  1. #!/usr/bin/env bash

  2. #!/usr/bin/bash

When I consulted the env man page, I get this definition:

 env - run a program in a modified environment

What does it mean?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Salah Eddine Taouririt
  • 24,925
  • 20
  • 60
  • 96
  • 12
    See [this question](http://unix.stackexchange.com/q/29608/10454) and [my answer](http://unix.stackexchange.com/a/29620/10454). – Keith Thompson May 03 '13 at 18:20
  • 8
    Who can tell me why this question is closed, " related to programming or software development" isen't ? – Salah Eddine Taouririt May 03 '13 at 19:04
  • 2
    I agree that it's not off-topic, but it's probably a duplicate of several other questions such as [this one](http://stackoverflow.com/q/2429511/827263). – Keith Thompson May 03 '13 at 19:33
  • 38
    This question should not have been marked as off-topic. It just needs 5 people with above a score of 3000 to mark it as "on-topic" and it can be reopened. It is a question - specifically about programming. – Danijel-James W Feb 03 '14 at 14:49
  • 1
    @DanijelJ: I agree that it's not off-topic, but it is a duplicate. We could vote to reopen and then vote to close it again as a duplicate. It hardly seems worth the effort. – Keith Thompson May 30 '14 at 14:39
  • 1
    Related: [Why is #!/usr/bin/env bash superior to #!/bin/bash?](http://stackoverflow.com/q/21612980/55075) at SO – kenorb Sep 28 '15 at 21:54
  • 4
    I'm shocked. Shocked to find that Linux documentation is rife with tautologies. https://xkcd.com/703/ http://git-man-page-generator.lokaltog.net/ – allyourcode Oct 29 '15 at 21:25
  • 5
    One major difference is that, depending on the system, `#!/usr/bin/bash` **won't work**. On my system (Ubuntu 17.04), `bash` is `/bin/bash`, and there is no `/usr/bin/bash`. The distinction between `/bin` and `/usr/bin` is largely arbitrary. And just to add to the frivolity, most systems put the `env` command in `/usr/bin`, but that's not guaranteed. – Keith Thompson Nov 13 '17 at 00:01
  • 1
    then symlink it. But it most definitely WAS on topic. – RichieHH Dec 10 '20 at 08:13

6 Answers6

488

Running a command through /usr/bin/env has the benefit of looking for whatever the default version of the program is in your current environment.

This way, you don't have to look for it in a specific place on the system, as those paths may be in different locations on different systems. As long as it's in your path, it will find it.

One downside is that you will be unable to pass more than one argument (e.g. you will be unable to write /usr/bin/env awk -f) if you wish to support Linux, as POSIX is vague on how the line is to be interpreted, and Linux interprets everything after the first space to denote a single argument. You can use /usr/bin/env -S on some versions of env to get around this, but then the script will become even less portable and break on fairly recent systems (e.g. even Ubuntu 16.04 if not later).

Another downside is that since you aren't calling an explicit executable, it's got the potential for mistakes, and on multiuser systems security problems (if someone managed to get their executable called bash in your path, for example).

#!/usr/bin/env bash #lends you some flexibility on different systems
#!/usr/bin/bash     #gives you explicit control on a given system of what executable is called

In some situations, the first may be preferred (like running python scripts with multiple versions of python, without having to rework the executable line). But in situations where security is the focus, the latter would be preferred, as it limits code injection possibilities.

user541686
  • 205,094
  • 128
  • 528
  • 886
Alec Bennett
  • 5,457
  • 2
  • 17
  • 18
  • 33
    Another drawback is that you can't pass an additional argument to the interpreter. – Keith Thompson May 03 '13 at 19:22
  • 2
    @KeithThompson : Incorrect info.. You can pass options to the underlying interpreter using /usr/bin/env! – Gaurav Agarwal May 30 '14 at 09:23
  • 7
    @GauravAgarwal: Not on my system. A script containing just this single line: `#!/usr/bin/env echo Hello` complains: `/usr/bin/env: echo Hello: No such file or directory`. Apparently it treats `echo Hello` as a single argument to `/usr/bin/env`. – Keith Thompson May 30 '14 at 14:44
  • @KeithThompson: Could you provide more info about your system? – Gaurav Agarwal Jun 06 '14 at 06:38
  • @KeithThompson, the GNU Coreutils version of `env` allows args to be passed to the command. The FreeBSD env also [seems to support it](http://www.freebsd.org/cgi/man.cgi?query=env). What are you using? :) – André Laszlo Dec 18 '14 at 15:38
  • 5
    @AndréLaszlo: The `env` command certainly allows arguments to be passed to the command. The issue is the semantics of the `#!` line, and that depends on the kernel. Recent Linux kernels do allow things like `#!/usr/bin/env command args`, but older Linux kernels and other systems don't. – Keith Thompson Dec 18 '14 at 15:42
  • Yeah, I just tested. Sorry, should have read your comment more carefully :) http://stackoverflow.com/questions/3306518/cannot-pass-an-argument-to-python-with-usr-bin-env-python – André Laszlo Dec 18 '14 at 15:43
  • 1
    Why are there backticks in the code block? Shouldn't they be removed? – Benjamin W. Jul 31 '17 at 18:10
  • 1
    Made the command stand out more? Honestly, it was four years ago, so I don't have a legit excuse. In a shell script, you're correct, they wouldn't be there, and admittedly I didn't dig in to SE markup as much back then. – Alec Bennett Aug 01 '17 at 18:47
  • A note: there is an assumption that env should be in /usr/bin/env in the most of systems. In many of them, bash is in /bin/bash, so the use of /usr/bin/env maybe just a small flexibility... another note is that in some (old?) systems, /usr is a different partition that can be not mounted, and maybe for that cause #!/bin/bash is usually used, not #!/usr/bin/bash – campisano Jun 29 '18 at 19:02
  • 1
    I've had problems with using env in my scripts and then adding those scripts to a crontab. Even running under the same user, the environment is different when executing it in the shell and it getting executed by cron. My script didn't run as a cron job. I think this is worth mentioning in this excellent answer. – Sebastián Grignoli Aug 11 '21 at 14:16
115

Using #!/usr/bin/env NAME makes the shell search for the first match of NAME in the $PATH environment variable. It can be useful if you aren't aware of the absolute path or don't want to search for it.

Dolphiniac
  • 1,632
  • 1
  • 9
  • 9
  • 27
    At least you have to know where env is :). – ᐅdevrimbaris Mar 22 '16 at 08:50
  • 2
    Excellent answer. Explains succinctly what the env shebang does, rather than saying "chooses the program based on your system configuration" – De Novo Nov 16 '18 at 17:57
  • Here is the situation I encountered. When I run the command "./script.sh" with "#!/usr/bin/env bash" in the first line of script.sh, it gives a feedback of "-bash: ./tools/dist_train.sh: Permission denied". When I run the command "bash ./script.sh". It can work well. Could you tell me why "#!/usr/bin/env NAME" doesn't work...? – Eric Kani Jul 31 '21 at 09:24
  • 3
    @EricKani: Most likely your `"./script.sh"` does have its executable flag unchecked (so you can't execute it on its own). `"bash ./script.sh"` happily accepts non-executable script files. (Solution: `chmod +x ./script.sh`) – DevSolar Nov 18 '21 at 11:06
58

If the shell scripts start with #!/bin/bash, they will always run with bash from /bin. If they however start with #!/usr/bin/env bash, they will search for bash in $PATH and then start with the first one they can find.

Why would this be useful? Assume you want to run bash scripts, that require bash 4.x or newer, yet your system only has bash 3.x installed and currently your distribution doesn't offer a newer version or you are no administrator and cannot change what is installed on that system.

Of course, you can download bash source code and build your own bash from scratch, placing it to ~/bin for example. And you can also modify your $PATH variable in your .bash_profile file to include ~/bin as the first entry (PATH=$HOME/bin:$PATH as ~ will not expand in $PATH). If you now call bash, the shell will first look for it in $PATH in order, so it starts with ~/bin, where it will find your bash. Same thing happens if scripts search for bash using #!/usr/bin/env bash, so these scripts would now be working on your system using your custom bash build.

One downside is, that this can lead to unexpected behavior, e.g. same script on the same machine may run with different interpreters for different environments or users with different search paths, causing all kind of headaches.

The biggest downside with env is that some systems will only allow one argument, so you cannot do this #!/usr/bin/env <interpreter> <arg>, as the systems will see <interpreter> <arg> as one argument (they will treat it as if the expression was quoted) and thus env will search for an interpreter named <interpreter> <arg>. Note that this is not a problem of the env command itself, which always allowed multiple parameters to be passed through but with the shebang parser of the system that parses this line before even calling env. Meanwhile this has been fixed on most systems but if your script wants to be ultra portable, you cannot rely that this has been fixed on the system you will be running.

It can even have security implications, e.g. if sudo was not configured to clean environment or $PATH was excluded from clean up. Let me demonstrate this:

Usually /bin is a well protected place, only root is able to change anything there. Your home directory is not, though, any program you run is able to make changes to it. That means malicious code could place a fake bash into some hidden directory, modify your .bash_profile to include that directory in your $PATH, so all scripts using #!/usr/bin/env bash will end up running with that fake bash. If sudo keeps $PATH, you are in big trouble.

E.g. consider a tool creates a file ~/.evil/bash with the following content:

#!/bin/bash

if [ $EUID -eq 0 ]; then
  echo "All your base are belong to us..."
  # We are root - do whatever you want to do
fi

/bin/bash "$@"

Let's make a simple script sample.sh:

#!/usr/bin/env bash

echo "Hello World"

Proof of concept (on a system where sudo keeps $PATH):

$ ./sample.sh
Hello World

$ sudo ./sample.sh
Hello World

$ export PATH="$HOME/.evil:$PATH"

$ ./sample.sh
Hello World

$ sudo ./sample.sh
All your base are belong to us...
Hello World

Usually the classic shells should all be located in /bin and if you don't want to place them there for whatever reason, it's really not an issue to place a symlink in /bin that points to their real locations (or maybe /bin itself is a symlink), so I would always go with #!/bin/sh and #!/bin/bash. There's just too much that would break if these wouldn't work anymore. It's not that POSIX would require these position (POSIX does not standardize path names and thus it doesn't even standardize the shebang feature at all) but they are so common, that even if a system would not offer a /bin/sh, it would probably still understand #!/bin/sh and know what to do with it and may it only be for compatibility with existing code.

But for more modern, non standard, optional interpreters like Perl, PHP, Python, or Ruby, it's not really specified anywhere where they should be located. They may be in /usr/bin but they may as well be in /usr/local/bin or in a completely different hierarchy branch (/opt/..., /Applications/..., etc.). That's why these often use the #!/usr/bin/env xxx shebang syntax.

Mecki
  • 125,244
  • 33
  • 244
  • 253
  • Thanks @Mecki, best answer by far, especially by demonstrating how easy it is to include the "All your base are belong to us..." part. A real black hat hacker would not add that part and only would try to exploit any vulnerabilities (be they technically or personally - see social engineering) I use the shebangs #!/bin/sh and #!/bin/bash - and if someone complains I tell them to either edit the script themselves or create a symlink in /bin - or ask their system administrator to create the symlinks in /bin . – Rava Aug 27 '22 at 20:56
  • Good answer. This is basically why it is a very bad practice to put `.` in your `PATH`, in the first place. Even if you have it in the last position in `PATH`, at it later might not be last any longer. – Anders Apr 26 '23 at 11:46
16

Instead of explicitly defining the path to the interpreter as in /usr/bin/bash/, by using the env command, the interpreter is searched for and launched from wherever it is first found. This has both upsides and downsides

Salah Eddine Taouririt
  • 24,925
  • 20
  • 60
  • 96
safay
  • 547
  • 5
  • 14
  • On most systems, they will be functionally the same, but it depends on the location of your bash and env executables. Not sure how this will affect environment variables, though. – safay May 03 '13 at 18:21
  • 2
    "It is possible to specify the interpreter without using env, by giving the full path to the interpreter. A problem is that on different computer systems, the exact path may be different. By instead using env, the interpreter is searched for and located at the time the script is run. This makes the script more portable, but also increases the risk that the wrong interpreter is selected because it searches for a match in every directory on the executable search path. It also suffers from the same problem in that the path to the env binary may also be different on a per-machine basis."-Wikipedia – Mike Clark May 03 '13 at 18:23
4

I find it useful, because when I didn't know about env, before I started to write script I was doing this:

type nodejs > scriptname.js #or any other environment

and then I was modifying that line in the file into shebang.
I was doing this, because I didn't always remember where is nodejs on my computer -- /usr/bin/ or /bin/, so for me env is very useful. Maybe there are details with this, but this is my reason

sudo97
  • 338
  • 1
  • 6
1

One reason is that it is portable across both Linux and BSD.

Scott Franco
  • 481
  • 2
  • 15
  • Could help with an example why it makes it portable. It is not only for compatibility between Linux and BSD but also between different distributions of Linux. But yes, you are right. – Anders Apr 26 '23 at 11:41
  • I found the reference to it when I needed to port to BSD. I verified it empirically on a couple of Linux distros. For BSD, it was tried on GhostBSD and Mac (which is a linux port of sorts). Obviously not a very satisfying answer. – Scott Franco Aug 22 '23 at 18:27