Node:Limitations of Builtins, Next:Limitations of Usual Tools, Previous:Special Shell Variables, Up:Portable Shell
No, no, we are serious: some shells do have limitations! :)
You should always keep in mind that any built-in or command may support
options, and therefore have a very different behavior with arguments
starting with a dash. For instance, the innocent echo "$word"
can give unexpected results when word
starts with a dash. It is
often possible to avoid this problem using echo "x$word"
, taking
the x
into account later in the pipe.
.
.
only with regular files (use test -f
). Bash
2.03, for instance, chokes on . /dev/null
. Also, remember that
.
uses PATH
if its argument contains no slashes, so if
you want to use .
on a file foo
in the current
directory, you must use . ./foo
.
!
!
, you'll have to rewrite your code.
break
break 2
, etcetera, is safe.
case
You don't need the final ;;
, but you should use it.
Because of a bug in its fnmatch
, bash
fails to properly
handle backslashes in character classes:
bash-2.02$ case /tmp in [/\\]*) echo OK;; esac bash-2.02$
This is extremely unfortunate, since you are likely to use this code to
handle UNIX or MS-DOS absolute paths. To work around this
bug, always put the backslash first:
bash-2.02$ case '\TMP' in [\\/]*) echo OK;; esac OK bash-2.02$ case /tmp in [\\/]*) echo OK;; esac OK
Some shells, such as Ash 0.3.8, are confused by empty
case
/esac
:
ash-0.3.8 $ case foo in esac; error-->Syntax error: ";" unexpected (expecting ")")
Many shells still do not support parenthesized cases, which is a pity
for those of us using tools that rely on balanced parentheses. For
instance, Solaris 2.8's Bourne shell:
$ case foo in (foo) echo foo;; esac error-->syntax error: `(' unexpected
echo
echo
is probably the most surprising source of
portability troubles. It is not possible to use echo
portably
unless both options and escape sequences are omitted. New applications
which are not aiming at portability should use printf
instead of
echo
.
Don't expect any option. See Preset Output Variables, ECHO_N
etc. for a means to simulate -c
.
Do not use backslashes in the arguments, as there is no consensus on
their handling. On echo '\n' | wc -l
, the sh
of
Digital Unix 4.0, MIPS RISC/OS 4.52, answer 2, but the Solaris'
sh
, Bash and Zsh (in sh
emulation mode) report 1.
Please note that the problem is truly echo
: all the shells
understand '\n'
as the string composed of a backslash and an
n
.
Because of these problems, do not pass a string containing arbitrary
characters to echo
. For example, echo "$foo"
is safe
if you know that foo's value cannot contain backslashes and cannot
start with -
, but otherwise you should use a here-document like
this:
cat <<EOF $foo EOF
exit
exit
is supposed to be $?
;
unfortunately, some shells, such as the DJGPP port of Bash 2.04, just
perform exit 0
.
bash-2.04$ foo=`exit 1` || echo fail fail bash-2.04$ foo=`(exit 1)` || echo fail fail bash-2.04$ foo=`(exit 1); exit` || echo fail bash-2.04$
Using exit $?
restores the expected behavior.
Some shell scripts, such as those generated by autoconf
, use a
trap to clean up before exiting. If the last shell command exited with
nonzero status, the trap also exits with nonzero status so that the
invoker can tell that an error occurred.
Unfortunately, in some shells, such as Solaris 8 sh
, an exit
trap ignores the exit
command's status. In these shells, a trap
cannot determine whether it was invoked by plain exit
or by
exit 1
. Instead of calling exit
directly, use the
AC_MSG_ERROR
macro that has a workaround for this problem.
export
export
dubs environment variable a shell
variable. Each update of exported variables corresponds to an update of
the environment variables. Conversely, each environment variable
received by the shell when it is launched should be imported as a shell
variable marked as exported.
Alas, many shells, such as Solaris 2.5, IRIX 6.3, IRIX 5.2, AIX 4.1.5
and DU 4.0, forget to export
the environment variables they
receive. As a result, two variables are coexisting: the environment
variable and the shell variable. The following code demonstrates this
failure:
#! /bin/sh echo $FOO FOO=bar echo $FOO exec /bin/sh $0
when run with FOO=foo
in the environment, these shells will print
alternately foo
and bar
, although it should only print
foo
and then a sequence of bar
s.
Therefore you should export
again each environment variable
that you update.
false
false
to exit with status 1: in the native Bourne
shell of Solaris 8, it exits with status 255.
for
for arg do echo "$arg" done
You may not leave the do
on the same line as for
,
since some shells improperly grok:
for arg; do echo "$arg" done
If you want to explicitly refer to the positional arguments, given the
$@
bug (see Shell Substitutions), use:
for arg in ${1+"$@"}; do echo "$arg" done
if
!
is not portable. Instead of:
if ! cmp -s file file.new; then mv file.new file fi
use:
if cmp -s file file.new; then :; else mv file.new file fi
There are shells that do not reset the exit status from an if
:
$ if (exit 42); then true; fi; echo $? 42
whereas a proper shell should have printed 0
. This is especially
bad in Makefiles since it produces false failures. This is why properly
written Makefiles, such as Automake's, have such hairy constructs:
if test -f "$file"; then install "$file" "$dest" else : fi
set
--
to specify
the end of the options (any argument after --
is a parameters,
even -x
for instance), but most shells simply stop the option
processing as soon as a non-option argument is found. Therefore, use
dummy
or simply x
to end the option processing, and use
shift
to pop it out:
set x $my_list; shift
shift
shift
ing a bad idea when there is nothing left to
shift, but in addition it is not portable: the shell of MIPS
RISC/OS 4.52 refuses to do it.
source
.
instead.
test
test
program is the way to perform many file and string
tests. It is often invoked by the alternate name [
, but using
that name in Autoconf code is asking for trouble since it is an M4 quote
character.
If you need to make multiple checks using test
, combine them with
the shell operators &&
and ||
instead of using the
test
operators -a
and -o
. On System V, the
precedence of -a
and -o
is wrong relative to the unary
operators; consequently, POSIX does not specify them, so using them
is nonportable. If you combine &&
and ||
in the same
statement, keep in mind that they have equal precedence.
You may use !
with test
, but not with if
:
test ! -r foo || exit 1
.
test
(files)
configure
scripts to support cross-compilation, they
shouldn't do anything that tests features of the build system instead of
the host system. But occasionally you may find it necessary to check
whether some arbitrary file exists. To do so, use test -f
or
test -r
. Do not use test -x
, because 4.3BSD does not
have it. Do not use test -e
either, because Solaris 2.5 does not
have it.
test
(strings)
test "string"
, in particular if string might
start with a dash, since test
might interpret its argument as an
option (e.g., string = "-n"
).
Contrary to a common belief, test -n string
and test
-z string
are portable, nevertheless many shells (such
as Solaris 2.5, AIX 3.2, UNICOS 10.0.0.6, Digital Unix 4 etc.) have
bizarre precedence and may be confused if string looks like an
operator:
$ test -n = test: argument expected
If there are risks, use test "xstring" = x
or test
"xstring" != x
instead.
It is frequent to find variations of the following idiom:
test -n "`echo $ac_feature | sed 's/[-a-zA-Z0-9_]//g'`" && action
to take an action when a token matches a given pattern. Such constructs
should always be avoided by using:
echo "$ac_feature" | grep '[^-a-zA-Z0-9_]' >/dev/null 2>&1 && action
Use case
where possible since it is faster, being a shell builtin:
case $ac_feature in *[!-a-zA-Z0-9_]*) action;; esac
Alas, negated character classes are probably not portable, although no
shell is known to not support the POSIX.2 syntax [!...]
(when in interactive mode, zsh
is confused by the
[!...]
syntax and looks for an event in its history because of
!
). Many shells do not support the alternative syntax
[^...]
(Solaris, Digital Unix, etc.).
One solution can be:
expr "$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action
or better yet
expr "x$ac_feature" : '.*[^-a-zA-Z0-9_]' >/dev/null && action
expr "Xfoo" : "Xbar"
is more robust than echo
"Xfoo" | grep "^Xbar"
, because it avoids problems when
foo
contains backslashes.
trap
trap
run when the script ends (either via an
explicit exit
, or the end of the script).
Although POSIX is not absolutely clear on this point, it is widely
admitted that when entering the trap $?
should be set to the exit
status of the last command run before the trap. The ambiguity can be
summarized as: "when the trap is launched by an exit
, what is
the last command run: that before exit
, or
exit
itself?"
Bash considers exit
to be the last command, while Zsh and
Solaris 8 sh
consider that when the trap is run it is
still in the exit
, hence it is the previous exit status
that the trap receives:
$ cat trap.sh trap 'echo $?' 0 (exit 42); exit 0 $ zsh trap.sh 42 $ bash trap.sh 0
The portable solution is then simple: when you want to exit 42
,
run (exit 42); exit 42
, the first exit
being used to
set the exit status to 42 for Zsh, and the second to trigger the trap
and pass 42 as exit status for Bash.
The shell in FreeBSD 4.0 has the following bug: $?
is reset to 0
by empty lines if the code is inside trap
.
$ trap 'false echo $?' 0 $ exit 0
Fortunately, this bug only affects trap
.
true
true
is portable.
Nevertheless, it's not always a builtin (e.g., Bash 1.x), and the
portable shell community tends to prefer using :
. This has a
funny side effect: when asked whether false
is more portable
than true
Alexandre Oliva answered:
In a sense, yes, because if it doesn't exist, the shell will produce an exit status of failure, which is correct forfalse
, but not fortrue
.
unset
unset
, nevertheless, because
it is extremely useful to disable embarrassing variables such as
CDPATH
, you can test for its existence and use
it provided you give a neutralizing value when unset
is
not supported:
if (unset FOO) >/dev/null 2>&1; then unset=unset else unset=false fi $unset CDPATH || CDPATH=:
See Special Shell Variables, for some neutralizing values. Also, see
Limitations of Builtins, documentation of export
, for
the case of environment variables.