6

EDIT: The question and the answers are correct, but the problem I stated here is not. The maintainer actually REFUSED to use /usr/bin/env as a solution and instead recreated the script as an sh one, and doing that broke other users installs. So, using env for finding bash is not POSIX, but is default "enough" to be considered a standard.

I'm having some errors on installing software on nixos (specifically the haskell stack ) and after some digging on internet, I found some examples of people having trouble with the specific path of installed software. Here an example where a maintainer reverted a modification to a /usr/bin/env solution on finding bash because it broke other users installations.

So, referring to env on a shebang without informing the absolute path of installation (relying on the system search path) is something that could be considered wrong?

I mean is ok using

#!env bash

instead of

#!/usr/bin/env bash

if the absolute path of installation for env itself is not default on all systems?

migrated from serverfault.com Feb 20 '17 at 3:02

This question came from our site for system and network administrators.

3

env is present in /usr/bin on virtually all Unix-like systems that are still extant in the 21st century. The two exceptions I'm aware of are SCO OpenServer (which still powers a few very old servers) and NextSTEP (there can't be much supported hardware that hasn't died out). For portability, use #!/usr/bin/env. Anything else is less portable (except #!/bin/sh, which carries its own problems and is only useful if you do plan to run your script on SCO).

POSIX and Single Unix don't define /usr/bin/env, or /bin/sh, or any absolute path other than /, /tmp, and a few entries in /dev. /usr/bin/env is not a formal Unix standard, but it is very much a de facto standard.

#!env is completely useless. Shebang lookup doesn't use the PATH variable, it's done by the kernel. (And if it did PATH lookup then you might as well write #!bash directly)

#!/usr/bin/env bash is perfectly fine for a bash script and I question the wisdom of the maintainer who rejected a change from #!/bin/bash. It looks like the maintainer was ruffled by the request and then didn't take it very seriously. Changing to use standard sh instead of bash is even better for portability, but apparently that didn't work (the script probably had a nonstandard construct left). If converting the script to work in other shells was not possible then the script should still have been changed to use #!/usr/bin/env bash instead of #!/bin/bash.

  • After seeing you answer, i re-read the post on github where the maintainer reverted a solution, and find out that it don`t reverted a usr/bin/env shebang, he created a binsh new script, that broke something on other users. To me it's clear now that: 1- the /usr/bin/env shebang is better, 2- the problem is made worse because of the maintainer and 3- I can't read. I will try to flag myself on the question or else at least make an edit to clarify this... sight. – flavio Feb 23 '17 at 7:45
4

POSIX doesn't specify how exactly the shebang is to be interpreted. Quoting from the exec family of functions' RATIONALE:

One common historical implementation is that the execl(), execv(), execle(), and execve() functions return an [ENOEXEC] error for any file not recognizable as executable, including a shell script. When the execlp() and execvp() functions encounter such a file, they assume the file to be a shell script and invoke a known command interpreter to interpret such files. This is now required by POSIX.1-2008. These implementations of execvp() and execlp() only give the [ENOEXEC] error in the rare case of a problem with the command interpreter's executable file. Because of these implementations, the [ENOEXEC] error is not mentioned for execlp() or execvp(), although implementations can still give it.

Another way that some historical implementations handle shell scripts is by recognizing the first two bytes of the file as the character string "#!" and using the remainder of the first line of the file as the name of the command interpreter to execute.

One potential source of confusion noted by the standard developers is over how the contents of a process image file affect the behavior of the exec family of functions. The following is a description of the actions taken:

  1. If the process image file is a valid executable (in a format that is executable and valid and having appropriate privileges) for this system, then the system executes the file.

  2. If the process image file has appropriate privileges and is in a format that is executable but not valid for this system (such as a recognized binary for another architecture), then this is an error and errno is set to [EINVAL] (see later RATIONALE on [EINVAL]).

  3. If the process image file has appropriate privileges but is not otherwise recognized:

    1. If this is a call to execlp() or execvp(), then they invoke a command interpreter assuming that the process image file is a shell script.

    2. If this is not a call to execlp() or execvp(), then an error occurs and errno is set to [ENOEXEC].

And earlier on, in the DESCRIPTION, it says the execlp and execvp shall run scripts with sh:

In the cases where the other members of the exec family of functions would fail and set errno to [ENOEXEC], the execlp() and execvp() functions shall execute a command interpreter and the environment of the executed command shall be as if the process invoked the sh utility using execl() as follows:

execl(<shell path>, arg0, file, arg1, ..., (char *)0);

where <shell path> is an unspecified pathname for the sh utility, file is the process image file, and for execvp(), where arg0, arg1, and so on correspond to the values passed to execvp() in argv[0], argv2, and so on.

So, these functions run the equivalent of sh file .... And when the script is run from a POSIX shell, the effects of the shebang are unspecified. See 2.1, Shell Introduction:

  1. The shell reads its input from a file (see sh), from the -c option or from the system() and popen() functions defined in the System Interfaces volume of POSIX.1-2008. If the first line of a file of shell commands starts with the characters "#!", the results are unspecified.

Zsh doesn't mind, bash does:

$ cat foo.sh
#! env perl
echo $0
$ zsh -c ./foo.sh
Can't locate object method "echo" via package "./foo.sh" (perhaps you forgot to load "./foo.sh"?) at ./foo.sh line 2.
$ bash -c ./foo.sh
bash: ./foo.sh: env: bad interpreter: No such file or directory

Don't rely on not specifying PATH lookups in the shebang. (I know I ran zsh -c ./foo.sh instead of zsh foo.sh, however, the intention here was to show what the shells do in executing commands can differ.)

  • That's very good answer! :) I can accept it but can't give a "plus", because i have no privileges to do so yet (I will never understand why they do that here...) I asked this on serverfault because i was trying to figure out if one could use env to circumvent non default installs, but that show I can't do that way. Need to think more. – flavio Feb 20 '17 at 5:43
  • @flavio use /bin/sh as the shebang, then at the start of the script, try to locate bash (or whatever it is you'd do with env) and use bash to run the script. – muru Feb 20 '17 at 5:45
  • That's arguably a bug in zsh. Bash and zsh both try to emulate the #! mechanism (in case they're running on some exotic system that doesn't support shebangs). Bash does it faithfully. Zsh performs a PATH lookup for the shebang program, which in principle is helpful but has the downside of being a zsh-specific thing. – Gilles Feb 21 '17 at 1:26
  • Note that the part of your answer about execlp and execvp running scripts with sh does not apply here. It would apply if 1. the caller was calling one of these functions rather than some other wrapper for the execve system call, and 2. the execve call returned ENOEXEC. But here execve doesn't return ENOEXEC, because foo.sh does have a valid format — it starts with a shebang. The kernel tries to execute env and that fails with ENOENT because there's no file called env. – Gilles Feb 21 '17 at 1:29
  • @Gilles hmm, I though valid format meant something like ELF or a.out, since it doesn't say an interpreter script is a valid format for execve. – muru Feb 21 '17 at 1:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.