14

Suppose the default shell for my account is zsh but I opened the terminal and fired up bash and executed a script named prac002.sh, which shell interpreter would be used to execute the script, zsh or bash? Consider the following example:

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % sudo cat /etc/passwd | grep papagolf
[sudo] password for papagolf: 
papagolf:x:1000:1001:Rex,,,:/home/papagolf:/usr/bin/zsh
# papagolf's default shell is zsh

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % bash
# I fired up bash. (See that '%' prompt in zsh changes to '$' prompt, indicating bash.)

papagolf@Sierra:~/My Files/My Programs/Learning/Shell$ ./prac002.sh 
Enter username : Rex
Rex
# Which interpreter did it just use?

**EDIT : ** Here's the content of the script

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % cat ./prac002.sh 
read -p "Enter username : " uname
echo $uname
15

Because the script does not begin with a #! shebang line indicating which interpreter to use, POSIX says that:

If the execl() function fails due to an error equivalent to the [ENOEXEC] error defined in the System Interfaces volume of POSIX.1-2008, the shell shall execute a command equivalent to having a shell invoked with the pathname resulting from the search as its first operand, with any remaining arguments passed to the new shell, except that the value of "$0" in the new shell may be set to the command name. If the executable file is not a text file, the shell may bypass this command execution. In this case, it shall write an error message, and shall return an exit status of 126.

That phrasing is a little ambiguous, and different shells have different interpretations.

In this case, Bash will run the script using itself. On the other hand, if you ran it from zsh instead, zsh would use sh (whatever that is on your system) instead.

You can verify that behaviour for this case by adding these lines to the script:

echo $BASH_VERSION
echo $ZSH_VERSION

You'll note that, from Bash, the first line outputs your version, while the second never says anything, no matter which shell you use.

  • If your /bin/sh is, say, dash, then neither line will output anything when the script is executed from zsh or dash.
  • If your /bin/sh is a link to Bash, you'll see the first line output in all cases.
  • If /bin/sh is a different version of Bash than you were using directly, you'll see different output when you run the script from bash directly and from zsh.

The ps -p $$ command from rools's answer will also show useful information about the command the shell used to execute the script.

  • I upvoted, however, it uses the DEFAULT SHELL if no shebang is specified, whatever default shell you have specified. This is why you should always, imho, specify a shebang. – thecarpy Jun 25 '17 at 11:18
  • 4
    It does not, which was really the entire point of the answer. Your second sentence is certainly true. – Michael Homer Jun 25 '17 at 19:18
  • you are right, bash runs it using itself, the default shell on most of my boxes happens to be .... bash, from there my confusion. I just tested on Solaris 8 with ksh as default shell, sure enough, bash runs it as bash ... learned something new today, thanks (upvoted!) ;-) – thecarpy Jun 26 '17 at 8:47
  • Thanks. Does the failure of execl() call happen when a shell script doesn't contains a shebang and is executed as scriptname? Does it not happen when a shell script is executed as bash scriptname? Does it not happen when a shell script contains a shebang and is executed as scriptname? – Tim Apr 23 '18 at 3:15
  • I made a post at unix.stackexchange.com/questions/439376/… – Tim Apr 23 '18 at 3:42
7

Since the file is not of any of the types of executable recognised by the system, and assuming you've got the permission to execute that file, the execve() system call will typically fail with a ENOEXEC (not an executable) error.

What happens then is up to the application and/or library function used to execute the command.

That can be for instance a shell, the execlp()/execvp() libc function.

Most other applications will use either of those when they run a command. They will invoke a shell for instance by way of the system("command line") libc function which will typically invoke sh to parse that command line (the path of which can be determined at compile time (like /bin/sh vs /usr/xpg4/bin/sh on Solaris)), or invoke the shell stored in $SHELL by themselves like vi with its ! command, or xterm -e 'command line' and many other commands (su user -c will invoke the user's login shell instead of $SHELL).

Generally, a shebang-less text file that doesn't start with # is considered as a sh script. Which sh it is will vary though.

execlp()/execvp(), upon execve() returning ENOEXEC will typically invoke sh on it. For systems that have more than one sh because they can conform to more than one standard, which sh it is will be typically determined at compilation time (of the application using execvp()/execlp() by linking a different blob of code which refers to a different path to sh). For instance, on Solaris, that will be either /usr/xpg4/bin/sh (a standard, POSIX sh) or /bin/sh (the Bourne shell (an antiquated shell) on Solaris 10 and older, ksh93 in Solaris 11).

When it comes to shells, there's a lot of variation. bash, AT&T ksh, the Bourne shell will typically interpret the script themselves (in a child process unless exec is used) after having simulated a execve(), that is unset all the unexported variables, closed all the close-on-exec fds, removed all the custom traps, aliases, functions... (bash will interpret the script in sh mode). yash will execute itself (with sh as argv[0] so in sh mode) to interpret it.

zsh, pdksh, ash-based shells will typically invoke sh (the path of which determined at compilation time).

For csh and tcsh (and the sh of some early BSDs), if the first character of the file is #, then they will execute themselves to interpret it, and sh otherwise. That goes back to a pre-shebang time where csh did recognise # as comments but not the Bourne shell, so the # was a hint that it was a csh script.

fish (at least version 2.4.0), just returns an error if execve() fails (it doesn't attempt to treat it as a script).

Some shells (like bash or AT&T ksh) will first try to heuristically determine whether the file is probably meant to be a script or not. So you may find that some shells will refuse to execute a script if it's got a NUL character in the first few bytes.

Also note that if execve() fails with ENOEXEC but the file does have a shebang line, some shells do try to interpret that shebang line themselves.

So a few examples:

  • When $SHELL is /bin/bash, xterm -e 'myscript with args' will have myscript interpreted by bash in sh mode. While with xterm -e myscript with args, xterm will use execvp() so the script will be interpreted by sh.
  • su -c myscript on Solaris 10 where root's login shell is /bin/sh and /bin/sh is the Bourne shell will have myscript interpreted by the Bourne shell.
  • /usr/xpg4/bin/awk 'BEGIN{system("myscript")' on Solaris 10 will have it interpreted by /usr/xpg4/bin/sh (same for /usr/xpg4/bin/env myscript).
  • find . -prune -exec myscript {} \; on Solaris 10 (using execvp()) will have it interpreted by /bin/sh even with /usr/xpg4/bin/find, even in a POSIX environment (a conformance bug).
  • csh -c myscript will have it interpreted by csh if it starts with #, with sh otherwise.

All in all, you can't be sure what shell will be used to interpret that script if you don't know how and by what it will be invoked.

In any case, read -p is bash-only syntax, so you'll want to make sure that script is interpreted by bash (and avoid that misleading .sh extension). Either you know the path of the bash executable and use:

#! /path/to/bash -
read -p ...

Or you can try and rely on a $PATH lookup of the bash executable (assuming bash is installed) by using:

#! /usr/bin/env bash
read -p ...

(env is almost ubiquitously found in /usr/bin). Alternatively, you can make it POSIX+Bourne compatible in which case you can use /bin/sh. All systems will have a /bin/sh. On most of them it will be (for the most part) POSIX-compatible, but you may still find now and then a Bourne shell there instead.

#! /bin/sh -
printf >&2 'Enter a user name: '
read user
printf '%s\n' "$user"
0

When you do not have any #! (called shebang) line, sh is used. To check that, you can run the following script.

ps -p $$
echo -n "The real shell is: "
realpath /proc/$$/exe

On my computer I get

  PID TTY          TIME CMD
13718 pts/16   00:00:00 sh
The real program is: /usr/bin/bash

even if my default shell is zsh. It uses bash since on my machine, the sh command is implemented by bash.

  • Why did you downvote that? Please explain or it is useless... – rools Jul 29 '18 at 7:45

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.