For a more extensive answer, read https://apple.stackexchange.com/questions/361870/what-are-the-practical-differences-between-bash-and-zsh/361957#361957
There's already been quite a bit of activity on the topic on other Stack Exchange sites. My experience of switching from bash to zsh, as far as can remember (it was years ago²), is that I didn't miss a single thing. I gained a lot; here are what I think are the simple zsh-specific features that I use most:
The zsh feature I most miss when I occasionally use bash is autocd: in zsh, executing a directory means changing to it, provided you turn on the autocd
option.⁴
Another very useful feature is the fancy globbing. The hieroglyphscharacters are a bit hard to remember but extremely convenient (as in, it's often faster to look them up than to write the equivalent find
command). A few of the simpler examples:
foo*~*.bak
= all matches for foo*
except those matching *.bak
foo*(.)
= only regular files matching foo*
foo*(/)
= only directories matching foo*
foo*(-@)
= only dangling symbolic links matching foo*
foo*(om[1,10])
= the 10 most recent files matching foo*
foo*(Lm+1)
= only files of size > 1MB
dir/**/foo*
= foo*
in the directory dir
and all its subdirectories, recursively⁴
For fancy renames, the zmv
builtin can be handy. For example, to copy every file
to file.bak
: zmv -C '(*)(#q.)' '$1.bak'
Both bash and zsh have a decent completion system that needs to be turned on explicitly (. /etc/bash_completion
or autoload -U compinit; compinit
). Zsh's is much more configurable and generally fancier.
If you run zsh without a .zshrc
, it starts an interactive menu that lets you choose configuration options. (Some distributions may disable this; in that case, run autoload zsh-newuser-install; zsh-newuser-install
.) I recommend enabling the recommended history options, turning on (“new-style”) completion, and turning on the “common shell options” except beep
. Later, configure more options as you discover them.
²At the time programmable completion was zsh's killer feature, but bash acquired it soon after.
⁴Features that bash acquired only in version 4 (so were not widely available at the time this answer was posted, and are not available on the system-provided bash on macOS) are in smaller type.
In the 21st century, especially if you're targeting machines that are likely to have bash or zsh, you can count on type
being available. (It didn't exist in extremely old unices, as in, from the 1970s or early 1980s.) You can't count on its output meaning anything, but you can count on its returning 0 if there is a command by that name and nonzero otherwise.
which
isn't standard and is unreliable in practice. type
is the recommended alternative. whereis
suffers from the same problems as which
and is less common. whence
is specific to ksh and zsh.
When that's possible, it would be more reliable to test the existence of a command and test whether its behavior looks reasonable. For example, test the presence of a suitable version of bash by running bash -c 'somecommand'
, e.g.
# Test for the `-v` operator (which appeared in bash 4.2)
if bash -c 'test -v HOME' 2>/dev/null; then …
Today you can count on almost everything in the Singe UNIX specification version 2 (except for exotic stuff like Fortran and SCCS, which are optional anyway). You can count on most of version 3, too, but this isn't completely implemented everywhere yet. Version 4 support is sketchier. If you're going to read these specs, I recommend reading version 3, which is a lot more readable and less ambiguous than version 2.
For examples as to how to detect system specificities, look at autoconf and at configure
scripts of various software.
See also Resources for portable shell programming for more tips.
Best Answer
I'll stick to scripting features. Rich interactive features (command line edition, completion, prompts, etc.) tend to be very different, achieving similar effects in wholly incompatible ways. What features are in zsh and missing from bash, or vice versa? gives a few pointers on interactive use.
The closest thing to bash would be ATT ksh93 or mksh (the Korn shell and a clone). Zsh also has a subset of features but you would need to run it in ksh emulation mode, not in zsh native mode.
I won't list POSIX features (which are available in any modern
sh
shell), nor relatively obscure features, nor as mentioned above features for interactive use. Observations are valid as of bash 4.2, ksh 93u and mksh 40.9.20120630 as found on Debian wheezy.Shell syntax
Quoting
$'…'
(literal strings with backslash interpolation) is available in ksh93 and mksh. `$"…" (translated strings) is bash-specific.Conditional constructs
Mksh and ksh93 have
;&
to fall through in acase
statement, but not;;&
to test subsequent cases. Mksh has;|
for that, and recent mksh allows;;&
for compatibility.((…))
arithmetic expressions and[[ … ]]
tests are ksh features. Some conditional operators are different, see “conditional expressions” below.Coprocesses
Ksh and bash both have coprocesses but they work differently.
Functions
Mksh and ksh93 support the
function name {…}
syntax for function definitions in addition to the standardname () {…}
, but usingfunction
in ksh changes scoping rules, so stick toname () …
to maintain compatibility. The rules for allowed characters in function names vary; stick to alphanumerics and_
.Brace expansion
Ksh93 and mksh support brace expansion
{foo,bar}
. Ksh93 supports numeric ranges{1..42}
but mksh doesn't.Parameter expansion
Ksh93 and mksh support substring extraction with
${VAR:offset}
and${VAR:offset:length}
, but not case folding like${VAR^}
,${VAR,}
, etc. You can do case conversion withtypeset -l
andtypeset -u
in both bash and ksh.They support replacement with
${VAR/PATTERN/STRING}
or${VAR/PATTERN//STRING}
. The quoting rules for STRING are slightly different, so avoid backslashes (and maybe other characters) in STRING (build a variable and use${VAR/PATTERN/$REPLACEMENT}
instead if the replacement contains quoting characters).Array expansion (
${ARRAY[KEY]}
,"${ARRAY[@]}"
,${#ARRAY[@]}
,${!ARRAY[@]}
) work in bash like in ksh.${!VAR}
expanding to${OTHERVAR}
when the value ofVAR
isOTHERVAR
(indirect variable reference) is bash-specific (ksh does something different with${!VAR}
). To get this double expansion in ksh, you need to use a name reference instead (typeset -n VAR=OTHERVAR; echo "$VAR"
).${!PREFIX*}
works the same.Process substitution
Process substitution
<(…)
and>(…)
is supported in ksh93 but not in mksh.Wildcard patterns
The ksh extended glob patterns that need
shopt -s extglob
to be activated in bash are always available in ksh93 and mksh.Mksh doesn't support character classes like
[[:alpha:]]
.IO redirection
Bash and ksh93 define pseudo-files
/dev/tcp/HOST/PORT
and/dev/udp/HOST/PORT
, but mksh doesn't.Expanding wildcards in a redirection in scripts (as in
var="*.txt"; echo hello >$a
writing toa.txt
if that file name is the sole match for the pattern) is a bash-specific feature (other shells never do it in scripts).<<<
here-strings work in ksh like in bash.The shortcut
>&
to redirect syntax errors is also supported by mksh but not by ksh93.Conditional expressions
[[ … ]]
double bracket syntaxThe double bracket syntax from ksh is supported by both ATT ksh93 and mksh like in bash.
File operators
Ksh93, mksh and bash support the same extensions to POSIX, including
-a
as an obsolete synonym of-e
,-k
(sticky),-G
(owned by egid),-O
(owner by euid),-ef
(same file),-nt
(newer than),-ot
(older than).-N FILE
(modified since last read) isn't supported by mksh.Mksh doesn't have a regexp matching operator
=~
. Ksh93 has this operator, and it performs the same matching as in bash, but doesn't have an equivalent ofBASH_REMATCH
to retrieve matched groups afterwards.String operators
Ksh93 and mksh support the same string comparison operators
<
and>
as bash as well as the==
synonym of=
. Mksh doesn't use locale settings to determine the lexicographic order, it compares strings as byte strings.Other operators
-v VAR
to test if a variable is defined is bash-specific. In any POSIX shell, you can use[ -z "${VAR+1}" ]
.Builtins
alias
The set of allowed character in alias names isn't the same in all shells. I think it's the same as for functions (see above).
builtin
Ksh93 has a builtin called
builtin
, but it doesn't execute a name as a built-in command. Usecommand
to bypass aliases and functions; this will call a builtin if one exists, otherwise an external command (you can avoid this withPATH= command error_out_if_this_is_not_a_builtin
).caller
This is bash-specific. You can get a similar effect with
.sh.fun
,.sh.file
and.sh.lineno
in ksh93. In mksh there's at lastLINENO
.declare
,local
,typeset
declare
is a bash-specific name for ksh'stypeset
. Usetypeset
: it also works in bash.Mksh defines
local
as an alias fortypeset
. In ksh93, you need to usetypeset
(or define an alias).Mksh has no associative arrays (they're slated for an as yet unreleased version).
I don't think there's an exact equivalent of bash's
typeset -t
(trace function) in ksh.cd
Ksh93 doesn't have
-e
.echo
Ksh93 and mksh process the
-e
and-n
options like in bash. Mksh also understands-E
, ksh93 doesn't treat it as an option. Backslash expansion is off by default in ksh93, on by default in mksh.enable
Ksh doesn't provide a way to disable builtin commands. To avoid a builtin, look up the external command's path and invoke it explicitly.
exec
Ksh93 has
-a
but not-l
. Mksh has neither.export
Neither ksh93 nor mksh has
export -n
. Usetypeset +x foo
instead, it works in bash and ksh.Ksh doesn't export functions through the environment.
let
let
is the same in bash and ksh.mapfile
,readarray
This is a bash-specific feature. You can use
while read
loops or command substitution to read a file and split it into an array of lines. Take care ofIFS
and globbing. Here's the equivalent ofmapfile -t lines </path/to/file
:printf
printf
is very similar. I think ksh93 supports all of bash's format directives. mksh doesn't support%q
or%(DATE_FORMAT)T
; on some installations,printf
isn't an mksh builtin and calls the external command instead.printf -v VAR
is bash-specific, ksh always prints to standard output.read
Several options are bash-specific, including all the ones about readline. The options
-r
,-d
,-n
,-N
,-t
,-u
are identical in bash, ksh93 and mksh.readonly
You can declare a variable as read-only in Ksh93 and mksh with the same syntax. If the variable is an array, you need to assign to it first, then make it read-only with
readonly VAR
. Functions can't be made read-only in ksh.set
,shopt
All the options to
set
andset -o
are POSIX or ksh features.shopt
is bash-specific. Many options concern interactive use anyway. For effects on globbing and other features enabled by some options, see the section “Options” below.source
This variant of
.
exists in ksh as well. In bash and mksh,source
searches the current directory afterPATH
, but in ksh93, it's an exact equivalent of.
.trap
The
DEBUG
pseudo-signal isn't implemented in mksh. In ksh93, it exists with a different way to report information, see the manual for details.type
In ksh,
type
is an alias forwhence -v
. In mksh,type -p
does not print the path to the executable, but a human-readable message; you need to usewhence -p COMMAND
instead.Options
shopt -s dotglob
— don't ignore dot files in globbingTo emulate the
dotglob
option in ksh93, you can setFIGNORE='@(.|..)'
. I don't think there's anything like this in mksh.shopt -s extglob
— ksh extended glob patternsThe
extglob
option is effectively always on in ksh.shopt -s failglob
— error out if a glob pattern matches nothingI don't think this exists in either mksh or ksh93. It does in zsh (default behavior unless
null_glob
orcsh_null_glob
are set).shopt -s globstar
—**/
recursive globbingKsh93 has recursive globbing with
**/
, enabled withset -G
. Mksh doesn't have recursive globbing.shopt -s lastpipe
— run the last command of a pipeline in the parent shellKsh93 always runs the last command of a pipeline in the parent shell, which in bash requires the
lastpipe
option to be set. Mksh always runs the last command of a pipeline in a subshell.shopt -s nocaseglob
,shopt -s nocasematch
— case-insensitive patternsMksh doesn't have case-insensitive pattern matching. Ksh93 supports it on a pattern-by-pattern basis: prefix the pattern with
~(i)
.shopt -s nullglob
— expand patterns that match no file to an empty listMksh doesn't have this. Ksh93 supports it on a pattern-by-pattern basis: prefix the pattern with
~(N)
.Variables
Obviously most of the
BASH_xxx
variables don't exist in ksh.$BASHPID
can be emulated with the costly but portablesh -c 'echo $PPID'
, and has been recently added to mksh.BASH_LINE
is.sh.lineno
in ksh93 andLINENO
in mksh.BASH_SUBSHELL
is.sh.subshell
in ksh93.Mksh and ksh93 both source the file given in
ENV
when they start up.EUID
andUID
don't exist in ksh93. Mksh calls themUSER_ID
andKSH_UID
; it doesn't haveGROUPS
.FUNCNAME
andFUNCNEST
don't exist in ksh. Ksh93 has.sh.fun
and.sh.level
. Functions declared withfunction foo { …; }
(no parentheses!) have their own name in$0
.GLOBIGNORE
exists in ksh93 but with a different name and syntax: it's calledFIGNORE
, and it's a single pattern, not a colon-separated list. Use a@(…|…)
pattern. Ksh'sFIGNORE
subsumes bash's, with a wholly different syntax.Ksh93 and mksh have nothing like
HOSTTYPE
,MACHTYPE
andOSTYPE
. NorSHELLOPTS
orTIMEFORMAT
.Mksh has
PIPESTATUS
, but ksh93 doesn't.Mksh and ksh93 have
RANDOM
.