HOWTO: Detect bash from shell script
Solution 1
There are a bunch of environment variables that you can look at but many of them will not detect if a different shell is spawned from bash. Consider the following:
bash$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: -bash, PS1: bash$ , prompt:
bash$ csh
[lorien:~] daveshawley% echo "SHELL: $SHELL, shell: $shell, \$0: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: /bin/tcsh, ARGV[0]: csh, PS1: bash$ , prompt: [%m:%c3] %n%#
[lorien:~] daveshawley% bash -r
bash$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: sh, PS1: bash$ , prompt:
bash$ zsh
% echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: zsh, PS1: % , prompt: %
% ksh
$ echo "SHELL: $SHELL, shell: $shell, ARGV[0]: $0, PS1: $PS1, prompt: $prompt"
SHELL: /bin/bash, shell: , ARGV[0]: ksh, PS1: bash$ , prompt:
There are a number of variables specific to the various shells except that they have a habit of being inherited by sub-shells which is where the environment thing really breaks. The only thing that almost works is ps -o command -p $$
. This technically gives you the command name that the shell is running as. In most cases this will work... since applications are started with some variant of the exec
system call and it allows for the name of the command and the executable to differ, it is possible for this to fail as well. Consider:
bash$ exec -a "-csh" bash
bash$ echo "$0, $SHELL, $BASH"
-csh, /bin/bash, /bin/bash
bash$ ps -o command -p $$
COMMAND
-csh
bash$
Another trick is to use lsof -p $$ | awk '(NR==2) {print $1}'
. This is probably as close as you can get if you are lucky enough to have lsof
handy.
Solution 2
This works also
[ -z "$BASH_VERSION" ] && return
Solution 3
Here is a nice way:
if test -z "$(type -p)" ; then echo bash ; else echo sh ; fi
and you can of course replace the "echo" statements with anything you want.
=================
Discussion:
The
$SHELL
variable indicates the user's preferred shell ... which tells you nothing about the shell that is running at the moment.Testing
$BASH_VERSION
is a 99% good idea, but it could fail if some wise-guy sticks a variable of that name into the sh environment. Furthermore, it doesn't tell you much about which non-bash shell is running.The
$(type -p)
method is super-easy, and works even if some wise guy creates a file called "-p" in your$PATH
. Furthermore, it can be used as the basis for a 4-way discrimination, or 80% of a 5-way discrimination, as discussed below.Putting a hash-bang i.e.
#!
at the top of your script does not guarantee that it will be fed to the interpreter of your choice. For example, my~/.xinitrc
gets interpreted by/bin/sh
no matter what hash-bang (if any) appears at the top.Good practice is to test some feature that reliably exists in both languages, but behaves differently. In contrast, it would not be safe in general to try the feature you want and see whether it fails. For instance, if you want to use the built-in
declare
feature and it's not present, it could run a program, and that has unlimited downside potential.- Sometimes it is reasonable to write compatible code, using the lowest-common-denominator feature set ... but sometimes it isn't. Most of those added features were added for a reason. Since these interpreters are «almost» Turing-complete it is «almost» guaranteed to be possible to emulate one with the other ... possible, but not reasonable.
- There are two layers of incompatibility: syntax and semantics. For instance, the if-then-else syntax for csh is so different from bash that the only way to write compatible code would be to do without if-then-else statements altogether. That's possible, but it imposes a high cost. If the syntax is wrong, the script won't execute at all. Once you get past that hurdle, there are dozens of ways in which reasonable-looking code produces different results, depending on which dialect of interpreter is running.
For a large, complicated program, it does not make sense to write two versions. Write it once, in the language of your choice. If somebody starts it under the wrong interpreter, you can detect that and simply
exec
the right interpreter.-
A 5-way detector can be found here:
https://www.av8n.com/computer/shell-dialect-detect
It can discriminate:
- bash
- bsd-csh
- dash
- ksh93
- zsh5
Furthermore, on my Ubuntu Xenial box, that 5-way check also covers the following:
- ash is a symlink to dash
- csh is a symlink to /bin/bsd-csh
- ksh is a symlink to /bin/ksh93
- sh is a symlink to dash
Solution 4
I think this would be the most practical and cross shell compatible
/proc/self/exe --version 2>/dev/null | grep -q 'GNU bash' && USING_BASH=true || USING_BASH=false
Explanation:
/proc/self
will always point to the current executing process, for example, running the following reveals the pid of readlink
it self (not the shell which executed readlink)
$ bash -c 'echo "The shell pid = $$"; echo -n "readlink (subprocess) pid = "; readlink /proc/self; echo "And again the running shells pid = $$"'
Results in:
The shell pid = 34233
readlink (subprocess) pid = 34234
And again the running shells pid = 34233
Now:
/proc/self/exe
is a symbolic link to the running executable
Example:
bash -c 'echo -n "readlink binary = "; readlink /proc/self/exe; echo -n "shell binary = "; readlink /proc/$$/exe'
results in:
readlink binary = /bin/readlink
shell binary = /bin/bash
And here is the results running in dash and zsh, and running bash through a symlink, and even through a copy.
[email protected]:~$ cp /bin/bash ./my_bash_copy
[email protected]:~$ ln -s /bin/bash ./hello_bash
[email protected]:~$
[email protected]:~$ dash -c '/proc/self/exe -c "readlink /proc/$$/exe"; zsh -c "/proc/self/exe --version"; ./hello_bash --version | grep bash; ./my_bash_copy --version | grep bash'
/bin/dash
zsh 5.0.7 (x86_64-pc-linux-gnu)
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
[email protected]:~$ dash -c '/proc/self/exe -c "readlink /proc/$$/exe"; zsh -c "/proc/self/exe --version"; ./hello_bash --version | grep bash; ./my_bash_copy --version | grep bash'
Solution 5
to improve @Dumble0re answer to support more shell dialets:
# original detect shell dialet, see: https://www.av8n.com/computer/shell-dialect-detect
# improvement version: https://gist.github.com/gzm55/028912a3d4c2846790c7438d0863fd7f
# `&&` will defeat the errexit option, see: http://www.binaryphile.com/bash/2018/08/13/approach-bash-like-a-developer-part-15-strict-mode-caveats.html
eval `echo ! : 2>/dev/null` : && echo "[ERROR] must not be executed or sourced by csh!" && exit 64
__DO_NOT_SUPPORT_FISH__=1
# Now that csh and fish has been excluded, it is safe to continue
__SHELL_DIALECT__=
case "${__SHELL_DIALECT__:=$(
PATH="/dev/null/$$" set +e
PATH="/dev/null/$$" export PATH="/dev/null/$$"
type -p 2>/dev/null >/dev/null
echo $? $(type declare 2>/dev/null; echo err=$?)
)}" in
"0 "*"not found err=1") __SHELL_DIALECT__=mksh ;;
"0 "*"not found err=127") __SHELL_DIALECT__=busybox ;; # ash busybox various
"0 "*"shell builtin err="*) __SHELL_DIALECT__=bash4 ;;
"1 "*"reserved word err="*) __SHELL_DIALECT__=zsh5 ;;
"1 "*"shell builtin err="*) __SHELL_DIALECT__=bash3 ;;
"2 err="*) __SHELL_DIALECT__=ksh93 ;;
"127 "*"not found err="*) __SHELL_DIALECT__=dash ;; # ash debian various
"127 err=127") __SHELL_DIALECT__=ash-bsd ;; # ash bsd-sh various
*) __SHELL_DIALECT__="unknown:$__SHELL_DIALECT__" ;;
esac
# detect bash posix mode, on bash, SHELLOPTS is a read only variable
case "$__SHELL_DIALECT__:${SHELLOPTS-}:" in
bash*:posix:*) __SHELL_DIALECT__="$__SHELL_DIALECT__-posix" ;;
*) ;;
esac
echo "__SHELL_DIALECT__=[$__SHELL_DIALECT__]"
The importanted is that, bash 3
has different detecting method, which should be the default /bin/sh
on MacOS.
Another, when detecting csh, the improved method avoids depending on the location of test
at /usr/bin/test
, the MacOS does not has this path.

Comments
-
Kan-Ru Chen 6 months
The scenario is that users are asked to source a script file:
$ source envsetup.sh
This script file may use bash only feature so we have detect the running shell is bash or not.
For other shells that share common syntax with bash, for example, sh, zsh, ksh, I'd like to report a warning.
What is the most reliable way to detect the current shell across Linux, Cygwin, OS X?
What I know is $BASH, but I am wondering the chances it could fail.
-
michelson over 12 yearsTry this from within bash before you rely on
$SHELL
:csh -c 'echo "$SHELL"'
-
Spectator6 over 12 yearsMy impression of the question is that the script needs to know the login shell. Using bash as the login shell, running that command should return bash, which is expected.
-
Kan-Ru Chen over 12 yearsNot the login shell. If you login use bash then change to csh, $SHELL is remain bash, but
source
would fail. -
Kan-Ru Chen over 12 years
ps o command= $$
also works on Linux, now I have to check cygwin.. But you know, the ps command has so many variants.. -
michelson over 12 years
ps -o command -p $$
is probably the most portable form but it still can fail. This is actually a tricky one to get to work reliably. -
David Terei almost 12 yearsCan also do this for zsh, so
if [ -n "${BASH_VERSION}" ]; then ... elif [ -n "${ZSH_VERSION}" ]; then ... fi
-
gcb over 9 yearshave the script:
#!/bin/sh \n echo $BASH_VERSION
and even run it with sh or csh from a bash shell:$sh ./test.sh
and you will get the env var from the user prefered bash version.... totally misleading the running script -
hohonuuli about 7 yearsIf you're running in fish shell, then
$$
is not the alias for the pid. Instead, in fish you would runps -o command -p %self
. PITA. -
Gert van den Berg about 4 yearsFor Linux that seems like a good idea... Not sure if it is portable to OS X or Cygwin as asked in the question (I know that on FreeBSD /proc is not mounted by default...)
-
Alan Berezin almost 3 years
lsof -p $$ | awk '(NR==2) {print $1}'
is not reliable on Darwin sops -o command -p $$
is the best alternative. -
xdhmoore about 2 years7 years later, while I understand this, it doesn't seem very practical to me. In contrast to feature-detection in browsers, figuring out how to test a variety of shell features sounds significantly more challenging to me than using something like Modernizr or detecting the shell.