I know about this, and in fact I wrote an alternative solution for GNU Coreutils env in 2017: a complete patch with updated documentation and test cases.
Here is a problem: interpreters take arguments such as options ... but, separately, so do interpreted programs. Sometimes, you'd like to control both.
I made an extension to env whereby you could do:
#!/usr/bin/env :interp:--foo:{}:--bar
This would find the "interp" interpreter using a PATH search and then pass it the arguments --foo <scriptname> --bar, followed by arguments that came from the invocation of the hash-bang script. If the special argument {} does not appear, then interp will be invoked with --foo --bar <scriptname>: the script name is not relocated into the arguments.
However, the maintainer expressed a preference for compatibility with FreeBSD -S.
I took a look at the requirements and saw that FreeBSD's env -S was doing a whole lot of stuff: interpreting C-like character escape sequences like \n, and interpolating dollar-sign-sigiled variables like $USER. In spite of all that, didn't see the feature of being able to insert the script name into the middle of the arguments, like in my solution.
It amounted to throwing away my patch and implementing some FreeBSD stuff I had no interest in and a lot of which I thought was a bad idea, while failing to achieve my original functionality. So, I just did the first part: throwing away my patch.
The typical use case for env -S is a script passing arguments to the interpreter (#!/usr/bin/env -S interp --foo → interp --foo ./scriptname), and the user can always pass arguments to the script (./scriptname --bar → interp --foo ./scriptname --bar).
But why would a script ever need to use a shebang to pass an argument like --bar to itself? Can’t it just modify its own argv, or act as if it had?
> It is useful because it allows the hash bang to specify some arguments after the script (which could be arguments belonging to the script rather than to the interpreter, for instance).
but doesn’t really explain why the script itself would want to specify that.)
In any case, compatibility is important here, in order for it to be possible to write cross-platform scripts.
Here is a use case not involving the motivation to pass canned options passed to the script.
Suppose that the interpreter is such that the script name must be passed as an option. For instance, Awk implementations are like this:
awk -f <script>
The problem is that this kind of option is allowed to be followed by more options.
awk -f <script> -Z # error under GNU awk: -Z is an invalid option
See where this is headed? If we have
#!/usr/bin/awk -f
awk script here
and we call this as
$ ./script -Z
it will pass that option to Awk. But the script wanted to handle that option!
$ ./script -- -Z # process -Z as argument to the script
and wouldn't it be nice if we could hide this "--" argument in the hash bang?
With my proposal you could do that:
#!/usr/bin/env :awk:-f:{}:--
Now speaking of Awk, GNU awk has solved this particular problem itself. It has the option -E/--exec which works like -f, but is the last option to be processed.
(Any of these kinds of issues can be solved locally for a given interpreter, if you control its implementation.)
You could always fixup all of your scripts so they point to the correct point in PATH. this also has the benefit of notifying you if a program doesn't exist. Do note that env is unportable (freebsd has -P I think), which makes parsing it a PITA, identifying where the program actually is, is slightly challenging. If you ask me its better to throw the program to sh and let it eval to whatever. sh is the true portable launcher.
I wrote [0] in lua a while back (mind the comments), didn't destroy anything I think, I wrote [1] just now in gawk, so not as recommended.
The writer of the hash bang script retains the control not to have PATH lookup take place: they write an interpreter name which contains one or more slashes.
I'm guessing that Zsh is doing this in its fallback strategy for when the exec fails.
The exec fails when a shell tries to run an executable file that has no hash bang header, but also in a case like this when the header doesn't give a path that resolves to an interpreter.
Without the #!echo, I'm guessing Zsh would have interpreted the file as as Zsh script.
I’d suspect the problem is a stripped down container where the profile isn’t setup how you’re expecting. The interactive shell (bash, I assume), will setup the profile/env properly. I’m not sure your entrypoint cmd will setup the environment, as IIRC, the inir process would normally take care of this setup. But an entrypoint in a container has different rules.
Perhaps if you started your entrypoint with “/bin/bash cmd”??
If PATH isn't set up right then #!/usr/bin/env interpreter -S arg, which everyone uses, also won't work. It doesn't amount to a good argument why #!interpreter arg shouldn't be able to do that path search without the env shim or its need for the -S mechanism.
Fix that one, and you can use
Problem solved; no need to mess with the file system divisions.For that matter ... the stupid hash bang mechanism itself should do PATH resolution, maybe, you know?