On env Shebangs
scriptingosx.com
scriptingosx.com
I generally agree on the wisdom of "it depends and consider context", but..
An absolute path is going to assume others have the same setup.
The downside claimed for using #!/usr/bin/env seems to be:
- that the PATH needs to be setup correctly for interactive/non-interactive shells. (Which will be true of just executing that command in a script, anyway).
- and that the program might be a different version than expected (which an absolute path doesn't solve either).
Using #!/usr/bin/env is a much better default to go with, and will be the better option almost all the time.
Certain absolute path locations are standardized by POSIX. Including the absolute path of /usr/bin/env.
> and that the program might be a different version than expected (which an absolute path doesn't solve either).
...and those programs in those standard locations are required by those same standards to behave ABI-compatibly with their behavior at time of standardization.
Which is why e.g. /bin/bash bothers to strip out features when called as /bin/sh — the interpreter at /bin/sh must be no less and no more than the particular feature-set of the Bourne shell version that POSIX was standardized upon.
But on Linux we also don't have to fight with a 15 year old bash or "compromising" a system when installing something into /bin. It feels lile OS X genuinely doesn't sound like a good UNIX to administer.
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p imagemagick
Voila. A (mostly) self-contained shell script, dependencies auto-installed when needed.I've resisted the pull of the Nix side so far...
There's a similar package that auto-pulls dependencies for Swift scripts, but it requires you to explicitly state the external dependencies as comment annotations. The example given is:
#!/usr/bin/swift sh
import Foundation
import PromiseKit // @mxcl ~> 6.5
firstly {
after(.seconds(2))
}.then {
after(.milliseconds(500))
}.done {
print("notice: two and a half seconds elapsed")
exit(0)
}
RunLoop.main.run()
https://github.com/mxcl/swift-shBut, of course, it isn't a silver bullet...
1. You still have to have a sane PATH. A fair amount of the Nix install-related issues that get opened are PATH problems, and you can also run into problems with PATH in cron/launchd (as @tyingq noted).
2. You still have to know what the script depends on. This can get tricky beyond small scripts you wrote yourself. (I write a tool for discovering and ~linking/resolving external dependencies in Shell scripts, https://github.com/abathur/resholve. As I've been working on converting some of nixpkgs' existing Shell packages to use it, I almost always find dependencies the initial packager missed.)
If you invoke nix-shell with --pure, then it'll erase most user state — PATH included — prior to invoking the script. I don't find much use for this, but it would be useful to ensure nothing that isn't an explicit dependency gets used.
$ nix-shell
nix-shell: command not foundThe env trick is useful when you have some programming language that is not commonly packaged. Users might have it in /usr/local/bin or some completely personal place that's in their PATH: /home/joe/bin or what have you.
Their shell script guidelines are pretty helpful for someone wanting to familiarize themselves with portable posix shell scripting. There's some style nits in there too, but the other stuff can be helpful if you're looking for a well-traveled path.
https://github.com/git/git/blob/v2.35.0/Documentation/Coding...
Please do write Python/Perl instead.
https://pubs.opengroup.org/onlinepubs/007908799/
put it into a #!/bin/sh file and have it work everywhere.
Good luck with #!/usr/bin/env python followed by code written to 1997 specs.
A script written for Python 3.5 (which was released 7 years ago) is very portable and will even work on non-Unix platforms like Windows. If it's not there, it is easier to compile than the entire suite of Unix tools. In addition, it has proper error handling...
Exceptions:
Asyncio has been in active development and was considered "provisional" for a long time, but is now stabilizing. Asyncio code that predates async/await syntax will not run on new versions of Python (this maybe can be converted with automated tools).
Type annotations have also been in active development and was considered "provisional" for a long time, and is also now stabilizing. In a future version (maybe 3.11?) a lot of `from typing import ___` imports will break and need to be replaced (this can mostly be converted with automated tools).
Several modules in the standard library are considered "dead batteries" and will be removed in an upcoming version. See <https://peps.python.org/pep-0594/>, and for discussion <https://discuss.python.org/t/pep-594-take-2-removing-dead-ba...>. This is probably the worst aspect for forward-compatibility, but it seems necessary to ensure the health of the CPython project going forward (unless of course enterprises decide to start contributing back some faction of the billions upon billions that they make on the backs of the Python Software Foundation every year). Expect community members to spin off these libraries
[1] https://stackoverflow.com/questions/10813538/shebang-line-li...
#!/usr/bin/env bash
set -eu
# Your script here> The -S option allows specifing [sic] multiple parameters in a script.
> [...]
> Without the '-S' parameter the script will likely fail with:
> . . . /usr/bin/env: 'perl -w -T': No such file or directory
I actually ran into this problem last week and just used the absolute path with an argument (/usr/bin/python3 -u) instead of passing an argument to env, and I added a TODO comment to figure out how to do this better. Now you made me find the right way :)
In TXR Lisp, I invented a novel way of doing this, which I call the "hash bang null hack".
If you put a NUL character into the hash bang line, then TXR will process arguments after that character. The NUL character is a string terminator, and so the operating system kernel doesn't see it; it thinks that's the end of the hash bang line.
So for instance (^@ represents NUL):
#!/usr/bin/env txr^@arg arg arg
This hack is not necessary, of course; a given language interpreter can do this in any number of ways. E.g. arguments in a comment in the following line. The convention could be, say, that if the next line also starts with #!, it holds parameters:
#!/usr/bin/env my-language
#!--args to my language
I didn't do it this way because the language doesn't use # comments (so hacking argument recognition into comment processing is out) and I didn't want to complicate the hash bang logic to dealing with two or more lines: everything is handled in the one line and that's it.Another solution is to rely on env having the BSD -S option, which specifies a string that becomes multiple arguments.
#!/usr/bin/env -S program --option arg
the hash bang mechanism will pass "program --option arg" as a single argument to env, whose -S option will split that into multiple arguments. You might run into a limit on the hash bang header. E.g. if the given operating system reads only, say, 32 bytses of the file (really ancient Unix system), in which it looks for the hash bang line, then you have only that much space to cram into that -S argument.The -S option implemented in BSD unixes has a lot of bells and whistles: you can use escape characters in it to encode control characters and $ to expand environment variables. E.g. silly example:
#!/usr/bin/env -S $USER/bin/interp
would use every user's own "interp" in their own bin directory to run the program.-S is not portable; it's not specified in POSIX. Newer versions of GNU Coreutils have it (but not with all the BSD features, I think). If you maintain a programing language, it behooves you to solve this problem for the users, so the only nonstandard thing they rely on is your language, without any collateral nonstandard stuff.
No it doesn’t. It’s just there to specify which interpreter to use instead of the current shell, so you can rub it like any other executable. Has nothing to do with portability.
#!/usr/bin/env python3
which allows the file to be run with whichever python interpreter the user has in their PATH, instead of requiring the interpreter to be in a particular location.