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
3 Answers 3
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 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.kasperd– kasperd2018年12月15日 13:48:41 +00:00Commented Dec 15, 2018 at 13:48 -
@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.Michael Homer– Michael Homer2018年12月15日 17:20:04 +00:00Commented Dec 15, 2018 at 17:20
-
the ZSH_VERSION does not always return something, the solution proposed below (i.e. to use
realpath /proc/$$/exe
is the most portable solution.efx– efx2020年03月30日 08:39:53 +00:00Commented Mar 30, 2020 at 8:39 -
1@efx
$ZSH_VERSION
should always be undefined in this context./proc
is non-standard, as isrealpath
, sorealpath /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.Michael Homer– Michael Homer2020年03月30日 08:48:02 +00:00Commented Mar 30, 2020 at 8:48
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 havemyscript
interpreted bybash
insh
mode. While withxterm -e myscript with args
,xterm
will useexecvp()
so the script will be interpreted bysh
. su -c myscript
on Solaris 10 whereroot
's login shell is/bin/sh
and/bin/sh
is the Bourne shell will havemyscript
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 (usingexecvp()
) 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 bycsh
if it starts with#
, withsh
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"
To check which shell is used, 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, sh command is implemented by bash and zsh picks sh if no shebang is specified.
You must log in to answer this question.
Explore related questions
See similar questions with these tags.
. prac002.sh
, assuming your script is in the current dir.. ./prac002.sh
and it will run with the current shell, that is, dot (.
), space ( ) followed by the path of your script. It is called dot-sourcing your script. ;-)