4 ms·
Huh. I wish I had known this before. NixOS is annoying because everything is weird and symlinked and so I find myself fairly frequently making the mistake of
by tombert 11mo ago
Huh. I wish I had known this before.
NixOS is annoying because everything is weird and symlinked and so I find myself fairly frequently making the mistake of writing `#!/bin/bash`, only to be told it can't find it, and I have to replace the path with `/run/current-system/sw/bin/bash`.
Or at least I thought I did; apparently I can just have done `#!bash`. I just tested this, and it worked fine. You learn something new every day I guess.
- miffe 11mo agoSeems like it only works in zsh, not bash or fish
- tombert 11mo ago[tombert@puter:~/testscript]$ ./myscript.sh bash: ./myscript.sh: bash: bad interpreter: No such file or directory You are right. Appears to only work with zsh. I will resume being annoyed then.
- adastra22 11mo agoIs this UNIX?
- tombert 11mo agoThis is NixOS, so no, it's Linux. I guess I just hoped it would work on Linux as well.
- adastra22 11mo agoLinux is UNIX in the context of my question. On Linux the shebang is actually handled by the kernel. When you load a binary and attempt to execute it with a syscall, the kernel reads the first few bytes of the binary. If it is an ELF header, it executes the machine code as you would expect. If the first two bytes are "#!", then it interprets it as a shebang header and loads the specified binary to interpret it. Again, this is kernel code. I admit I'm a bit confused as to why it didn't work on your system. This shouldn't be handled at the shell level.
- adastra22 11mo agoThe kernel interprets the shebang line, not the shell.
- jolmg 11mo agoIt is possible for the shell to handle it. From zshall(1): > If the program is a file beginning with ‘#!', the remainder of the first line specifies an interpreter for the program. The shell will execute the specified interpreter on operating systems that do not handle this executable format in the kernel. Taking a quick look at the source in Src/exec.c: execve(pth, argv, newenvp); // [...] if ((eno = errno) == ENOEXEC || eno == ENOENT) { // [...] if (ct >= 2 && execvebuf[0] == '#' && execvebuf[1] == '!') { // [...] (pprog = pathprog(ptr2, NULL))) { I guess at some point someone added that `|| eno == ENOENT` and the docs weren't updated.
- opello 11mo agoI did a little digging and found that the `|| eno == ENOENT` was added quite a bit earlier[1] than the actual pathprog lookup[2]. While I could find the "issue discussion" for the pathprog change[3] I wasn't able to find it for the ENOENT addition, which was kind of interesting and frustrating--[4] is the `X-Seq` mentioned in the commit but that seems to be inconsistent or incorrect for the actual cross-reference, and nearby in time wasn't helpful either. [1] https://sourceforge.net/p/zsh/code/ci/29ed6c7e3ab32da20f528aeba4bb76635f1a3b06/ https://sourceforge.net/p/zsh/code/ci/29ed6c7e3ab32da20f528a... [2] https://sourceforge.net/p/zsh/code/ci/29ed6c7e3ab32da20f528aeba4bb76635f1a3b06/ https://sourceforge.net/p/zsh/code/ci/29ed6c7e3ab32da20f528a... [3] https://www.zsh.org/mla/workers/2010/msg00522.html https://www.zsh.org/mla/workers/2010/msg00522.html [4] https://www.zsh.org/mla/workers/2000/msg01168.html https://www.zsh.org/mla/workers/2000/msg01168.html
- tombert 11mo agoI'm not sure the reason then, but they're definitely right; it works fine with zsh, doesn't work with bash. I wrote a test script to try it myself. I don't have fish installed and can't be bothered to go that far, but I suspect they're right about that as well.
- Crestwave 11mo ago`#!/usr/bin/env bash` is the most portable form for executing it from $PATH
- hamandcheese 11mo agoIs this meaningfully more portable than #!bash though?
- tombert 11mo agoIn a sibling thread someone pointed out that #!bash doesn't actually work if you're calling it from bash, and appears to only work with zsh. I just tried it and they were absolutely right, so `#!/usr/bin/env bash` is definitely more portable in that it consistently works.
- saurik 11mo agoThis mechanism doesn't do a PATH lookup: #!bash would only work if bash was located in your current working directory.
- Ferret7446 11mo agoI raise you a https://www.felesatra.moe/blog/2021/07/03/portable-bash-shebang https://www.felesatra.moe/blog/2021/07/03/portable-bash-sheb... Pedantic, but "#!" and "portable" don't belong in the same sentence
- Crestwave 11mo agoThe script you posted is only portable in theory and not in practice. Executing it while using a non-POSIX shell like Elvish (even without having it as a default shell) makes it immediately fail. Meanwhile, `#!/usr/bin/env` is completely portable in the practical sense. Even systems with non-standard paths like NixOS, Termux and GoboLinux patch in support for it specifically.
- nflekkhnnn 11mo agoAnything other than ”#!/usr/bin/env bash” is doomed to fail at some time.
- normie3000 11mo agoAnd is this shebang guaranteed to work always? Why isn't it more common?
- eyelidlessness 11mo agoIt’s quite common, although I probably see it used more frequently to invoke other (non-shell) scripting languages.
- int_19h 11mo agoIt's guaranteed to work provided that Bash is in the path. It's very common for Python. Less so for Bash for two reasons: because the person who writes the script references /bin/sh instead (which is required to be there) even when they are writing bash-isms, or because the person who writes the script assumes that Bash is universally available as /bin/bash.
- jolmg 11mo agoBecause /bin is the standard location for bash. The only one that breaks that expectation is NixOS (and maybe GuixSD?), apparently. I'm surprised they didn't symlink /bin or put a stub. Last time I tried NixOS was like 10 years ago. I thought there was a /bin/bash, but maybe it was just a /bin/sh? Other interpreters like python, ruby, etc. have more likelyhood of being used with "virtual environments", so it's more common to use /usr/bin/env with them.
- tombert 11mo agoThey do symlink /bin/sh to be fair, and that's very often good enough for a lot of scripts. That's what I usually do if I don't need anything bash offers.
- 11mo ago
- saintfire 11mo agoYou can use `#!/usr/bin/env bash` on NixOS
- tombert 11mo agoI didn't know that actually. I'll start using that from this point forward.
- kreetx 11mo ago/usr/bin/env and /bin/sh are part of the POSIX standard, this is why NixOS has those available.
- jlokier 11mo ago> /usr/bin/env and /bin/sh are part of the POSIX standard, this is why NixOS has those available. Contrary to popular belief, those aren't in the POSIX standard. The following are not in the POSIX standard, they are just widely implemented: - "#!" line. - /bin/sh as the location of a POSIX compliant shell, or any shell. - /usr/bin/env as the location of an env program. - The -S option to env.
- Guvante 11mo agoI think you are using "not required by the POSIX standard" when you say "not in" which is not an accurate shorthand. #! is certainly in the POSIX standard as the exact topic of "is /bin/sh always a POSIX" shell is a discussion point (it is not guaranteed since there were systems that existed at the time that had a non-POSIX shell there)
- johnisgood 11mo agoAre they in POSIX? I do not think they are. All of them is a convention from what I remember. Shebang is a kernel feature, for example, and POSIX does define the sh shell language and utilities, but does not specify how executables are invoked by the kernel. Similarly, POSIX only requires that sh exists somewhere in the PATH, and the /bin/sh convention comes from the traditional Unix and FHS (Filesystem Hierarchy Standard), but POSIX does not mandate filesystem layout. ... and so on. Correct me if I am wrong, perhaps with citations?
- teo_zero 11mo ago> apparently I can just have done `#!bash` I think you're mixing two concepts: relative paths (which are allowed after #! but not very useful at all) and file lookup through $PATH (which is not done by the kernel, maybe it's some shell trickery).
- aaronmdjones 11mo ago> and file lookup through $PATH (which is not done by the kernel, maybe it's some shell trickery) It's libc. Specifically, system(3) and APIs like execvp(3) will search $PATH for the file specified before passing that to execve(2) (which is the only kernel syscall; all the other exec*() stuff is in libc).