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