On Wed, Feb 25, 2026, at 6:08 AM, Daniel Hatton wrote:
> On 24/02/2026 21:35, Zack Weinberg wrote:

>> My current plan is to say that #! /usr/bin/env <interpreter> should
>> be used for all interpreters other than /bin/sh.  If you have a
>> better idea I'm all ears.

> I've read the manpage for env twice, and I still don't think I
> entirely understand it.  The idea here is that it magically makes sure
> the PATH environment variable is set, so that the interpreter can be
> found without giving its full pathname on the hashbang line, right?

`#! /usr/bin/env perl` (or whatever other interpreter) is technically an
off-label use of the `env` utility, so I'm not surprised the manpage is
unhelpful.  Let me try to explain.

The kernel's implementation of `#!` (on all Unixes) doesn't look at the
PATH environment variable at all.  The interpreter path, after the
leading `#!`, _must_ be absolute.  But the kernel will recognize _one_
space-separated command line argument after the interpreter path, and
pass it along to the interpreter before the script name; the _intended_
use of that feature is to make `#! /usr/bin/awk -f` work.

`env` was originally created for the sake of csh.  In Bourne shell, you
can stick temporary environment variable settings before a command, e.g.

    LC_ALL=C ls

runs `ls` with `LC_ALL=C` in the environment, without changing the value
of LC_ALL for any subsequent command.  Csh doesn't have that feature,
but the `env` utility fakes it:

    env LC_ALL=C ls

sets LC_ALL=C in the environment of the `env` process and then execs
`ls`. For that to work, `env` has to do the PATH search for `ls` itself.
But, in the grandest Unix tradition, env doesn't insist on receiving any
environment variable arguments; `env ls` will cheerfully find and run
`ls` without changing the environment it inherited.

And that's the trick that makes `#! /usr/bin/env interpreter` work.
Suppose foo.pl begins with `#! /usr/bin/env perl`.  The kernel rewrites
a command line invocation

   "/path/to/foo.pl" "a" "b" "c"

(quotes to emphasize how the command line is split into argument vector
elements) as

   "/usr/bin/env" "perl" "/path/to/foo.pl" "a" "b" "c"

/usr/bin/env is an absolute path to a machine-code executable, so the
#! processor is done, the ELF loader takes over, loads and runs
/usr/bin/env.  env sees no environment variable settings, so it does the
PATH search for "perl" and finds, I dunno,
/gnu/store/x98jdz78m80ld103an9hvy0nqbp2capk-perl-5.40.0/bin/perl
or like that, and _it_ calls execve with

    "/gnu/store/x98jdz78m80ld103an9hvy0nqbp2capk-perl-5.40.0/bin/perl" \
        "/path/to/foo.pl" "a" "b" "c"

And thus the overall effect is the same as if the kernel would do a PATH
search when someone wrote just `#! perl` at the top of a script.

---

I do not love this technique, for several reasons, the most important of
which is that it _can_ potentially break scripts by making the
interpreter they use depend on the invoking user's PATH.  Suppose, for
instance, some globally installed utility has `#! /usr/bin/env python`,
expecting that to run the system's Python 2 interpreter, and then
someone runs that utility from inside a Python virtualenv where `python`
is Python 3.  In the worst case that can cause *silent data corruption*.
(I have serious beef with the Python people's official pronouncement
that it's OK for `python` to run Python 3 but that's not really relevant
right now.)

Because of that, when I get around to writing the discussion of #! that
the FHS draft currently has a placeholder for, I plan to say it's
preferable for *packaged* software to rewrite #! lines to point to the
specific interpreter that's expected.  However, *source* distributions
have no choice but to rely on PATH, since there's no way to predict
whether the best available version of Perl on some random system is in
/bin or /usr/bin or /usr/local/bin or /opt/csw/gnu/bin or a store path
or ...  So the guidance for upstream software authors kinda has to be
"if you want your #! scripts to work when run directly from a VCS
checkout, you have to use #! /usr/bin/env interpreter for everything
but /bin/sh".

I also plan to mandate env support the -S option, which is not in POSIX,
but without it anyone who wants to point a #! line at awk without knowing
the absolute path of the awk implementation is up a creek.

zw

Reply via email to