Which shell interpreter runs a script with no shebang?

11,878

Solution 1

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.

Solution 2

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"

Solution 3

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 shell 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.

Share:
11,878

Related videos on Youtube

7_R3X
Author by

7_R3X

Linux Lover, Open Source Lover; Crazy Programmer; Algorithm Designer; Cyber-Security Enthusiast and a Typical Computer Nerd. 3:) Security blogger at and the creator of Crackerscreed.org

Updated on September 18, 2022

Comments

  • 7_R3X
    7_R3X over 1 year

    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
    
  • thecarpy
    thecarpy almost 7 years
    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.
  • Michael Homer
    Michael Homer almost 7 years
    It does not, which was really the entire point of the answer. Your second sentence is certainly true.
  • thecarpy
    thecarpy almost 7 years
    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!) ;-)
  • Tim
    Tim about 6 years
  • Rob C
    Rob C over 5 years
    I have even seen csh be used when #! was missing. I certainly consider the absence of #! in the beginning of a script to be an error. And having this workaround in shells probably does more harm than good. But it's good that you point out what POSIX says about it, I certainly wasn't aware of that.
  • Michael Homer
    Michael Homer over 5 years
    @kasperd It’s certainly not an error, it’s the only standard kind of script. If it’s a POSIX sh script no-shebang is even the most portable way to write it (you don’t know where a compatible shell lives). Running it with csh would be non-conforming, though.
  • Mac
    Mac over 4 years
    Haters (silent anonymous downvoters) will hate. I learned something from your answer, so here's an upvote. :-)
  • gbruscatto
    gbruscatto about 4 years
    the ZSH_VERSION does not always return something, the solution proposed below (i.e. to use realpath /proc/$$/exe is the most portable solution.
  • gbruscatto
    gbruscatto about 4 years
    Best reliable solution
  • Michael Homer
    Michael Homer about 4 years
    @efx $ZSH_VERSION should always be undefined in this context. /proc is non-standard, as is realpath, so realpath /proc/$$/exe is not portable at all, though it will work on e.g. many (but not all) systems with GNU coreutils. The answer to the question at hand is that there is no specific implementation you can generally rely upon.
  • Kusalananda
    Kusalananda about 4 years
    This was likely downvoted because it is partially wrong. The shell used is not sh under all circumstances, as is evident from other answers.
  • rools
    rools almost 4 years
    Where did I say that? I never said sh is the used shell.