'How to recognize whether bash or dash is being used within a script?
I'm writing a bash script and it throws an error when using "sh" command in Ubuntu (it seems it's not compatible with dash, I'm learning on this subject). So I would like to detect if dash is being used instead of bash to throw an error.
How can I detect it in a script context?. Is it even possible?
Solution 1:[1]
You can check for the presence of shell-specific variables:
For instance, bash
defines $BASH_VERSION
.
Since that variable won't be defined while running in dash
, you can use it to make the distinction:
[ -n "$BASH_VERSION" ] && isBash=1
Afterthought: If you wanted to avoid relying on variables (which, conceivably, could be set incorrectly), you could try to obtain the ultimate name of the shell executable running your script, by determining the invoking executable and, if it is a symlink, following it to its (ultimate) target.
The shell function getTrueShellExeName()
below does that; for instance, it would return 'dash'
on Ubuntu for a script run with sh
(whether explicitly or via shebang #!/bin/sh
), because sh
is symlinked to dash
there.
Note that the function's goal is twofold:
- Be portable:
- Work with all POSIX-compatible (Bourne-like) shells,
- across at least most platforms, with respect to what utilities and options are used - see caveats below.
- Work in all invocation scenarios:
- sourced (whether from a login shell or not)
- executed stand-alone, via the shebang line
- executed by being passed as a filename argument to a shell executable
- executed by having its contents piped via stdin to a shell executable
Caveats:
On at least one platform - macOS -
sh
is NOT a symlink, even though it is effectivelybash
. There, the function would return'sh'
in a script run withsh
.The function uses
readlink
, which, while not mandated by POSIX, is present on most modern platforms - though with differing syntax and features. Therefore, using GNUreadlink
's-f
option to find a symlink's ultimate target is not an option.
(The only modern platform I'm personally aware of that does not have areadlink
utility is HP-UX - see https://stackoverflow.com/a/24114056/45375 for a recursive-readlink implementation that should work on all POSIX platforms.)The function uses the
which
utility (except inzsh
, where it's a builtin), which, while not mandated by POSIX, is present on most modern platforms.Ideally,
ps -p $$ -o comm=
would be sufficient to determine the path of the executable underlying the process, but that doesn't work as intended when directly executing shell scripts with shebang lines on Linux, at least when using theps
implementation from theprocps-ng
package, as found on Ubuntu, for instance: there, such scripts report the script's file name rather than the underlying script engine's.Tip of the hat to ferdymercury for his help.
Therefore, the content of special file/proc/$$/cmdline
is parsed on Linux, whose firstNUL
-separated field contains the true executable path.
Example use of the function:
[ "$(getTrueShellExeName)" = 'bash' ] && isBash=1
Shell function getTrueShellExeName()
:
getTrueShellExeName() {
local trueExe nextTarget 2>/dev/null # ignore error in shells without `local`
# Determine the shell executable filename.
if [ -r /proc/$$/cmdline ]; then
trueExe=$(cut -d '' -f1 /proc/$$/cmdline) || return 1
else
trueExe=$(ps -p $$ -o comm=) || return 1
fi
# Strip a leading "-", as added e.g. by macOS for login shells.
[ "${trueExe#-}" = "$trueExe" ] || trueExe=${trueExe#-}
# Determine full executable path.
[ "${trueExe#/}" != "$trueExe" ] || trueExe=$([ -n "$ZSH_VERSION" ] && which -p "$trueExe" || which "$trueExe")
# If the executable is a symlink, resolve it to its *ultimate*
# target.
while nextTarget=$(readlink "$trueExe"); do trueExe=$nextTarget; done
# Output the executable name only.
printf '%s\n' "$(basename "$trueExe")"
}
Solution 2:[2]
Use $0 (that is the name of the executable of the shell being called).The command for example
echo $0
gives
/usr/bin/dash
for the dash and
/bin/bash
for a bash.The parameter substitution
${0##*/}
gives just 'dash' or 'bash'. This can be used in a test.
Solution 3:[3]
An alternative approach might be to test if a shell feature is available, for example to give an idea...
[[ 1 ]] 2>/dev/null && echo could be bash || echo not bash, maybe dash
Solution 4:[4]
echo $0 and [[ 1 ]] 2>/dev/null && echo
could be bash || echo not bash, maybe bash worked for me running Ubuntu 19.
Done slight Pascal, Fortran and C in school, but need to become fluent in shell script.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Fritz G. Mehner |
Solution 3 | Scrutinizer |
Solution 4 | Lore |