Go to the first, previous, next, last section, table of contents.
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.
fnmatch
, @command{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
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 section Preset Output Variables, ECHO_N
etc. for a means to simulate @option{-c}.
Do not use backslashes in the arguments, as there is no consensus on
their handling. On `echo '\n' | wc -l', the @command{sh} of
Digital Unix 4.0, MIPS RISC/OS 4.52, answer 2, but the Solaris'
@command{sh}, Bash and Zsh (in @command{sh} emulation mode) report 1.
Please note that the problem is truly @command{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 @command{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
$?
;
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 @command{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 @command{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.
#! /bin/sh echo $FOO FOO=bar echo $FOO exec /bin/sh $0when 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 @command{export} again each environment variable that you update.
for arg do echo "$arg" doneYou may not leave the
do
on the same line as for
,
since some shells improperly grok:
for arg; do echo "$arg" doneIf you want to explicitly refer to the positional arguments, given the `$@' bug (see section Shell Substitutions), use:
for arg in ${1+"$@"}; do echo "$arg" done
if ! cmp -s file file.new; then mv file.new file fiuse:
if cmp -s file file.new; then :; else mv file.new file fiThere are shells that do not reset the exit status from an @command{if}:
$ if (exit 42); then true; fi; echo $? 42whereas 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 x $my_list; shift
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 @option{-a} and @option{-o}. On System V, the
precedence of @option{-a} and @option{-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 @command{test}, but not with @command{if}:
`test ! -r foo || exit 1'.
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
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 expectedIf 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'`" && actionto 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 && actionUse
case
where possible since it is faster, being a shell builtin:
case $ac_feature in *[!-a-zA-Z0-9_]*) action;; esacAlas, negated character classes are probably not portable, although no shell is known to not support the POSIX.2 syntax `[!...]' (when in interactive mode, @command{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 && actionor 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.
$ cat trap.sh trap 'echo $?' 0 (exit 42); exit 0 $ zsh trap.sh 42 $ bash trap.sh 0The portable solution is then simple: when you want to `exit 42', run `(exit 42); exit 42', the first @command{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 @command{trap}.
$ trap 'false echo $?' 0 $ exit 0Fortunately, this bug only affects @command{trap}.
In a sense, yes, because if it doesn't exist, the shell will produce an exit status of failure, which is correct for @command{false}, but not for @command{true}.
CDPATH
or LANG
, you can test for its existence and use
it provided you give a neutralizing value when @command{unset} is
not supported:
if (unset FOO) >/dev/null 2>&1; then unset=unset else unset=false fi $unset CDPATH || CDPATH=:See section Special Shell Variables, for some neutralizing values. Also, see section Limitations of Shell Builtins, documentation of @command{export}, for the case of environment variables.
Go to the first, previous, next, last section, table of contents.