Home » Why is it better to use “#!/usr/bin/env NAME” instead of “#!/path/to/NAME” as my shebang?

Why is it better to use “#!/usr/bin/env NAME” instead of “#!/path/to/NAME” as my shebang?

Solutons:


It isn’t necessarily better.

The advantage of #!/usr/bin/env python is that it will use whatever python executable appears first in the user’s $PATH.

The disadvantage of #!/usr/bin/env python is that it will use whatever python executable appears first in the user’s $PATH.

That means that the script could behave differently depending on who runs it. For one user, it might use the /usr/bin/python that was installed with the OS. For another, it might use an experimental /home/phred/bin/python that doesn’t quite work correctly.

And if python is only installed in /usr/local/bin, a user who doesn’t have /usr/local/bin in $PATH won’t even be able to run the script. (That’s probably not too likely on modern systems, but it could easily happen for a more obscure interpreter.)

By specifying #!/usr/bin/python you specify exactly which interpreter will be used to run the script on a particular system.

Another potential problem is that the #!/usr/bin/env trick doesn’t let you pass arguments to the intrepreter (other than the name of the script, which is passed implicitly). This usually isn’t an issue, but it can be. Many Perl scripts are written with #!/usr/bin/perl -w, but use warnings; is the recommended replacement these days. Csh scripts should use #!/bin/csh -f — but csh scripts are not recommended in the first place. But there could be other examples.

I have a number of Perl scripts in a personal source control system that I install when I set up an account on a new system. I use an installer script that modifies the #! line of each script as it installs it in my $HOME/bin. (I haven’t had to use anything other than #!/usr/bin/perl lately; it goes back to times when Perl often wasn’t installed by default.)

A minor point: the #!/usr/bin/env trick is arguably an abuse of the env command, which was originally intended (as the name implies) to invoke a command with an altered environment. Furthermore, some older systems (including SunOS 4, if I recall correctly) didn’t have the env command in /usr/bin. Neither of these is likely to be a significant concern. env does work this way, a lot of scripts do use the #!/usr/bin/env trick, and OS providers aren’t likely to do anything to break it. It might be an issue if you want your script to run on a really old system, but then you’re likely to need to modify it anyway.

Another possible issue, (thanks to Sopalajo de Arrierez for pointing it out in comments) is that cron jobs run with a restricted environment. In particular, $PATH is typically something like /usr/bin:/bin. So if the directory containing the interpreter doesn’t happen to be in one of those directories, even if it’s in your default $PATH in a user shell, then the /usr/bin/env trick isn’t going to work. You can specify the exact path, or you can add a line to your crontab to set $PATH (man 5 crontab for details).

Kevin’s comment points out that Python’s virtualenv creates a special case, where the environment installs a Python interpreter in a special directory that’s inserted at the front of $PATH. For that particular environment (and perhaps others like it), the #!/usr/bin/env python trick (or python3?) is likely to be the best solution. (I haven’t used virtualenv myself.)

Because /usr/bin/env can interpret your $PATH, which makes scripts more portable.

#!/usr/local/bin/python

Will only run your script if python is installed in /usr/local/bin.

#!/usr/bin/env python

Will interpret your $PATH, and find python in any directory in your $PATH.

So your script is more portable, and will work without modification on systems where python is installed as /usr/bin/python, or /usr/local/bin/python, or even custom directories (that have been added to $PATH), like /opt/local/bin/python.

Portability is the only reason using env is preferred to hard coded paths.

Objective Criteria/Requirements:

In determining whether to use an absolute or logical (/usr/bin/env) path to an interpreter in a she-bang, there are (2) key considerations:

a) The interpreter can be found on target system

b) The correct version of interpreter can be found on target system

If we AGREE that “b)” is desirable, we also agree that:

c) It’s preferable our scripts fail rather than execute using an incorrect interpreter version and potentially achieve inconsistent results.

If we DON’T AGREE that “b)” matters, then any interpreter found will suffice.

Testing:

Since using a logical path- /usr/bin/env to the interpreter in the she-bang is the most extensible solution allowing the same script to execute successfully on target hosts with different paths to the same interpreter, we’ll test it- using Python due to its’ popularity- to see if it meets our criteria.

  1. Does /usr/bin/env live in a predictable, consistent location on POPULAR (not “every“) Operating Systems? Yes:
  • RHEL 7.5
  • Ubuntu 18.04
  • Raspbian 10 (“Buster”)
  • OSX 10.15.02
  1. Below Python script executed both inside and outside of virtual envelopes (Pipenv used) during tests:
    #!/usr/bin/env pythonX.x
    import sys
    print(sys.version)
    print('Hello, world!')
    
  2. The she-bang in the script was toggled by Python version number desired (all installed on same host):
  • #!/usr/bin/env python2
  • #!/usr/bin/env python2.7
  • #!/usr/bin/env python3
  • #!/usr/bin/env python3.5
  • #!/usr/bin/env python3.6
  • #!/usr/bin/env python3.7
  1. Expected results: that print(sys.version) = env pythonX.x. Each time ./test1.py was executed using a different installed Python version, the correct version specified in the she-bang was printed.

  2. Testing Notes:

  • Tests were exclusively limited to Python
  • Perl: Like Python- MUST live in /usr/bin according to the FHS
  • I’ve not tried every possible combination on every possible number of Linuxy/Unixy Operating System and version of each Operating System.

Conclusion:

Although it’s TRUE that #!/usr/bin/env python will use the first version of Python it finds in the user’s Path, we can moderate this behaviour by specifying a version number such as #!/usr/bin/env pythonX.x. Indeed, developers don’t care which interpreter is found “first“, all they care about is that their code is executed using the specified interpreter they know to be compatible with their code to ensure consistent results- wherever that may live in the filesystem

In terms of portability/flexibility, using a logical/usr/bin/env – rather than absolute path not only meets requirements a), b) & c) from my testing with different versions of Python, but also has the benefit of fuzzy-logic finding the same version interpreter even if they live at different paths on different Operating Systems. And although MOST distros respect the FHS, not all do.

So where a script will FAIL if binary lives in different absolute path than specified in shebang, the same script using a logical path SUCCEEDS as it keeps going until it finds a match, thereby offering greater reliability & extensibility across platforms.

Related Solutions

How to delete a non-empty directory in Terminal?

Use the below command : rm -rf lampp It deletes all files and folders contained in the lampp directory. In case user doesn't have the permission to delete the folder: Add sudo at the beginning of the command : sudo rm -rf folderName Otherwise, without sudo you...

mysql: Show GRANTs for all users

Nothing built-in. You have two options though: Use common_schema's sql_show_grants view. For example, you can query: SELECT sql_grants FROM common_schema.sql_show_grants; Or you can query for particular users, for example: SELECT sql_grants FROM...

Configurable values to MDB annotations

You can externalise the annotations into the ejb-jar.xml that you deploy in the META-INF of your jar file as follows: <?xml version="1.0" encoding="UTF-8"?> <ejb-jar version="3.0"> <enterprise-beans> <message-driven>...

How do I select which Apache MPM to use?

There are a number of MPM modules (Multi-Processing Modules), but by far the most widely used (at least on *nix platforms) are the three main ones: prefork, worker, and event. Essentially, they represent the evolution of the Apache web server, and the different...

Using var self = this or .bind(this)? [closed]

Things that would favor var self = this; bind isn't supported in IE8 and Safari5. If you aim to build a library or code that supports legacy browsers, then var self = this would be more cross-browser friendly. Sometimes, callbacks are bound to a certain context...

What is the difference between SSL vs SSH? Which is more secure?

SSL and SSH both provide the cryptographic elements to build a tunnel for confidential data transport with checked integrity. For that part, they use similar techniques, and may suffer from the same kind of attacks, so they should provide similar security (i.e....

How can I stop applications and services from running?

First Things First You may have some misconceptions about how Android works and what's really happening when a service is running or an app is in the background. See also: Do I really need to install a task manager? Most apps (e.g., ones you launch manually)...

How do I reset a lost administrative password?

By default the first user's account is an administrative account, so if the UI is prompting you for a password it's probably that person's user password. If the user doesn't remember their password you need to reset it. To do this you need to boot into recovery...

How can I use environment variables in Nginx.conf

From the official Nginx docker file: Using environment variables in nginx configuration: Out-of-the-box, Nginx doesn't support using environment variables inside most configuration blocks. But envsubst may be used as a workaround if you need to generate your...

Difference between .bashrc and .bash_profile

Traditionally, when you log into a Unix system, the system would start one program for you. That program is a shell, i.e., a program designed to start other programs. It's a command line shell: you start another program by typing its name. The default shell, a...

Custom query with Castle ActiveRecord

In this case what you want is HqlBasedQuery. Your query will be a projection, so what you'll get back will be an ArrayList of tuples containing the results (the content of each element of the ArrayList will depend on the query, but for more than one value will...

What is the “You have new mail” message in Linux/UNIX?

Where is this mail? It's likely to be in the spool file: /var/mail/$USER or /var/spool/mail/$USER are the most common locations on Linux and BSD. (Other locations are possible – check if $MAIL is set – but by default, the system only informs you about...

How can I find the implementations of Linux kernel system calls?

System calls aren't handled like regular function calls. It takes special code to make the transition from user space to kernel space, basically a bit of inline assembly code injected into your program at the call site. The kernel side code that "catches" the...

Is a composite index also good for queries on the first field?

It certainly is. We discussed that in great detail under this related question: Working of indexes in PostgreSQL Space is allocated in multiples of MAXALIGN, which is typically 8 bytes on a 64-bit OS or (much less common) 4 bytes on a 32-bit OS. If you are not...