11

I have read the following in this question:

bash supports a --posix switch, which makes it more POSIX-compliant. It also tries to mimic POSIX if invoked as sh.

The above quote assumes that /bin/sh is a link that points to /bin/bash.

But I don't quite understand what is meant by "invoked as sh".


Say that I have the following script which is called "script.sh":

#!/bin/bash
echo "Hello World"

Please tell me in each of the following cases whether the script will be run in normal bash mode or in POSIX mode (assume that I have executed the following commands in a terminal that is running bash):

  1. sh script.sh
  2. bash script.sh
  3. ./script.sh

Now say that I have the following script which is called "script.sh" (which is like the above script but without the shebang):

echo "Hello World"

Please tell me in each of the following cases whether the script will be run in normal bash mode or in POSIX mode (assume that I have executed the following commands in a terminal that is running bash):

  1. sh script2.sh
  2. bash script2.sh
  3. ./script2.sh

3 Answers3

19

Only cases 1 & 4 will run in POSIX mode (assuming that sh is bash and not some other implementation of sh). Any case that explicitly calls bash without --posix will not, whether from the shebang or not. Any case that explicitly calls sh will. The shebang is only used when no shell was explicitly started for the script already.

Case 6, if your terminal is running bash, will not run in POSIX mode and Bash will invoke it using itself. If your terminal were running zsh instead, case 6 would also run in POSIX mode. POSIX is ambiguous about exactly what should happen in that case, and Bash and zsh made different choices there. Bash invokes the script using itself, while zsh uses sh (whatever that happens to be). Other shells also vary on that point.


One simple way to tell what mode you're in is to make your script body:

kill -SIGHUP

which will fail with an error in POSIX mode, but give usage instructions for kill outside of it. This is an easy distinction and it works through a long range of Bash versions going back as far as you're likely to encounter.

Michael Homer
  • 76,565
  • 3
    There are other things that will force bash to run in POSIX mode like the POSIXLY_CORRECT environment variable or SHELLOPTS=posix. – Stéphane Chazelas Jan 11 '18 at 10:44
  • 1
    [ -o posix ] is a more obvious way to check that you're running in posix mode in bash (not in other shells (except yash), so you wouldn't want to do that in a sh script). POSIXLY_CORRECT=1 bash -c '[ -o posix ] && echo yes' outputs yes ` – Stéphane Chazelas Jan 11 '18 at 10:48
  • 2
    In case 6, bash invokes the script with itself in POSIX mode when it is itself in POSIX mode as POSIX requires. POSIX doesn't specify the she-bang mechanism, and 6 is the only POSIX way to have executable scripts and is clearly specified (in POSIX environments, the script is meant to be interpreted by a compliant sh utility). – Stéphane Chazelas Jan 11 '18 at 10:54
8

The "invoked as" refers to whatever it is that the process starting Bash puts in its "zeroth" command line argument, argv[0].

When a program is started with the exec*() syscalls, they don't really get to know the name of the binary file containing the program, but instead the calling process is free to put whatever it wants in there. Usually, of course, the name is taken from the filesystem, so if you run /bin/sh, that's what gets put there. And if /bin/sh is Bash, it doesn't have to be a symlink, it could be a hard link or just another copy of the shell program.

As an example of setting the "program name", Bash's exec command can set the zeroth argument with the -a option. (We could do the same with Perl, or directly with C, etc.)

Here, myname is a simple C program that just prints its zeroth argument, the name it sees itself:

$ ./myname 
I am ./myname
$ (exec -a something ./myname )
I am something
$ mv ./myname somename
$ ln -s somename othername
$ ./somename 
I am ./somename
$ ./othername
I am ./othername

Source:

#include <stdio.h>
int main(int argc, char *argv[]) {
    printf("I am %s\n", argv[0]);
    return 0;
}

But, to answer the numbered questions...

(1 & 4) running sh somescript will run whatever sh is on your PATH, probably /bin/sh but possibly something like /usr/xpg4/bin/sh.

  • If it's Bash, it runs in POSIX mode, since it sees the name sh.
  • If it is the Z shell or the Korn shell, it likewise sees the name sh, but it runs in "SH compatible" mode, which is aimed at being Bourne shell compatible and is subtly different to the full POSIX conformant mode in both of those shells.
  • It could be the Almquist shell, an actual Bourne shell, or something else, of course.

(2 & 5) Running bash somescript will run in regular Bash mode (again, it of course depends on what bash in your PATH is.)

(3) Here, the name of the script is given directly to the system call in place of the program file. The kernel reads the hashbang line and uses it to run the script.

(6) This is the complex one. It is similar to (3), but the system call for starting the program fails (ENOEXEC (Exec format error)), since there is no hashbang line. What happens next depends from whether the the shell that you are running is itself in POSIX mode. POSIX requires that a POSIX-conformant shell behave in a specific fashion in response to ENOEXEC. However, there is some leeway in "a command equivalent to having a shell invoked" which means that different shells do different things.

  • The Bourne Again shell re-runs itself in the same mode with the name of the script as its first command-line argument. In its POSIX-conformant mode, it is of course running itself in its POSIX-conformant mode, thus obeying the POSIX requirement to invoke a POSIX-conformant shell.
  • The Z shell, Almquist shell, and Korn shell run /bin/sh with the name of the script inserted before the other arguments as its first command-line argument. The Z shell, Almquist shell, and Korn shell are (attempting) to invoke a POSIX-conformant shell by dint of assuming that the /bin/sh program is one.
JdeBP
  • 68,745
ilkkachu
  • 138,973
4

The shell executed is either the one called in the command line or the one in the shebang (if the command line does not specify it).

So, versions 1 and 4 will run with sh, 2 and 5 with bash and 6 may not run if you are using sh (and some others) interactively. Bash does start the script. Ksh also. Zsh starts it as sh.

Only those started as sh will use the posix option if bash is linked to /bin/sh.

Add this line to your script to detect if any bash ksh or zsh version is running it:

echo "Hello World $BASH_VERSION $KSH_VERSION $ZSH_VERSION"