Where are zsh and mksh incompatible with bash?

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 a case 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 standard name () {…}, but using function in ksh changes scoping rules, so stick to name () … 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 with typeset -l and typeset -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 of VAR is OTHERVAR (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 to a.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 syntax

The 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 of BASH_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. Use command to bypass aliases and functions; this will call a builtin if one exists, otherwise an external command (you can avoid this with PATH= 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 last LINENO.

declare, local, typeset

declare is a bash-specific name for ksh's typeset. Use typeset: it also works in bash.

Mksh defines local as an alias for typeset. In ksh93, you need to use typeset (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. Use typeset +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 of IFS and globbing. Here's the equivalent of mapfile -t lines </path/to/file:

IFS=$'\n'; set -f
lines=($(</path/to/file))
unset IFS; set +f

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 and set -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 after PATH, 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 for whence -v. In mksh, type -p does not print the path to the executable, but a human-readable message; you need to use whence -p COMMAND instead.

Options

shopt -s dotglob — don't ignore dot files in globbing

To emulate the dotglob option in ksh93, you can set FIGNORE='@(.|..)'. I don't think there's anything like this in mksh.

shopt -s extglob — ksh extended glob patterns

The extglob option is effectively always on in ksh.

shopt -s failglob — error out if a glob pattern matches nothing

I don't think this exists in either mksh or ksh93. It does in zsh (default behavior unless null_glob or csh_null_glob are set).

shopt -s globstar — **/ recursive globbing

Ksh93 has recursive globbing with **/, enabled with set -G. Mksh doesn't have recursive globbing.

shopt -s lastpipe — run the last command of a pipeline in the parent shell

Ksh93 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 patterns

Mksh 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 list

Mksh 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 portable sh -c 'echo $PPID', and has been recently added to mksh. BASH_LINE is .sh.lineno in ksh93 and LINENO in mksh. BASH_SUBSHELL is .sh.subshell in ksh93.

Mksh and ksh93 both source the file given in ENV when they start up.

EUID and UID don't exist in ksh93. Mksh calls them USER_ID and KSH_UID; it doesn't have GROUPS.

FUNCNAME and FUNCNEST don't exist in ksh. Ksh93 has .sh.fun and .sh.level. Functions declared with function foo { …; } (no parentheses!) have their own name in $0.

GLOBIGNORE exists in ksh93 but with a different name and syntax: it's called FIGNORE, and it's a single pattern, not a colon-separated list. Use a @(…|…) pattern. Ksh's FIGNORE subsumes bash's, with a wholly different syntax.

Ksh93 and mksh have nothing like HOSTTYPE, MACHTYPE and OSTYPE. Nor SHELLOPTS or TIMEFORMAT.

Mksh has PIPESTATUS, but ksh93 doesn't.

Mksh and ksh93 have RANDOM.


This question is rather too broad.

Both mksh and zsh are shells that support a lot of GNU bash-specific extensions, but there are always some that are not understood.

zsh supports more stuff, but only in its native zsh mode, which is not compatible with POSIX shells (such as GNU bash, AT&T ksh93, mksh). Also, mksh is much leaner and faster and more portable.

In general, if this is your scripts we’re talking about, go ahead, just test them. (mksh does not support bash4-style associative arrays yet. The “declare” command is bash-specific, “typeset” is an equivalent. I am not familiar enough with zsh to state anything about that outright. ksh93 does not have “local” but also uses “typeset” for that.) But if this is about, say, running a bash-less Debian system, forget it. The existence of bash is part of the “promise” (API/ABI of the system), and much relies on it.

Disclaimer: I’m the mksh developer.

Tags:

Bash

Zsh

Mksh