File: autoconf.info, Node: Limitations of Builtins, Next: Limitations of Usual Tools, Prev: Shell Functions, Up: Portable Shell
11.14 Limitations of Shell Builtins
===================================
No, no, we are serious: some shells do have limitations! :)
You should always keep in mind that any builtin or command may
support options, and therefore differ in behavior with arguments
starting with a dash. For instance, even 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. Many of these limitations can be worked
around using M4sh (*note Programming in M4sh::).
‘.’
Use ‘.’ only with regular files (use ‘test -f’). Bash 2.03, for
instance, chokes on ‘. /dev/null’. Remember that ‘.’ uses ‘PATH’
if its argument contains no slashes. Also, some shells, including
bash 3.2, implicitly append the current directory to this ‘PATH’
search, even though Posix forbids it. So if you want to use ‘.’ on
a file ‘foo’ in the current directory, you must use ‘. ./foo’.
Not all shells gracefully handle syntax errors within a sourced
file. On one extreme, some non-interactive shells abort the entire
script. On the other, ‘zsh’ 4.3.10 has a bug where it fails to
react to the syntax error.
$ echo 'fi' > syntax
$ bash -c '. ./syntax; echo $?'
./syntax: line 1: syntax error near unexpected token `fi'
./syntax: line 1: `fi'
2
$ ash -c '. ./syntax; echo $?'
./syntax: 1: Syntax error: "fi" unexpected
$ zsh -c '. ./syntax; echo $?'
./syntax:1: parse error near `fi'
0
‘!’
The Unix version 7 shell did not support negating the exit status
of commands with ‘!’, and this feature is still absent from some
shells (e.g., Solaris 10 ‘/bin/sh’). Other shells, such as FreeBSD
‘/bin/sh’ or ‘ash’, have bugs when using ‘!’:
$ sh -c '! : | :'; echo $?
1
$ ash -c '! : | :'; echo $?
0
$ sh -c '! { :; }'; echo $?
1
$ ash -c '! { :; }'; echo $?
{: not found
Syntax error: "}" unexpected
2
Shell code like this:
if ! cmp file1 file2 >/dev/null 2>&1; then
echo files differ or trouble
fi
is therefore not portable in practice. Typically it is easy to
rewrite such code, e.g.:
cmp file1 file2 >/dev/null 2>&1 ||
echo files differ or trouble
In M4sh, the ‘AS_IF’ macro provides an easy way to write these
kinds of conditionals:
AS_IF([cmp -s file file.new], [],
[echo files differ or trouble])
This kind of rewriting is needed in code outside macro definitions
that calls other macros. *Note Common Shell Constructs::. It is
also useful inside macro definitions, where the “then” and “else”
branches might contain macro arguments.
More generally, one can always rewrite ‘! COMMAND’ as:
AS_IF([COMMAND], [(exit 1)])
‘&&’ and ‘||’
If an AND-OR list is not inside ‘AC_DEFUN’, and it contains calls
to Autoconf macros, it should be rewritten using ‘AS_IF’. *Note
Common Shell Constructs::. The operators ‘&&’ and ‘||’ have equal
precedence and are left associative, so instead of:
# This is dangerous outside AC_DEFUN.
cmp a b >/dev/null 2>&1 &&
AS_ECHO([files are same]) >$tmpfile ||
AC_MSG_NOTICE([files differ, or echo failed])
you can use:
# This is OK outside AC_DEFUN.
AS_IF([AS_IF([cmp a b >/dev/null 2>&1],
[AS_ECHO([files are same]) >$tmpfile],
[false])],
[AC_MSG_NOTICE([files differ, or echo failed])])
‘{...}’
Bash 3.2 (and earlier versions) sometimes does not properly set
‘$?’ when failing to write redirected output of a compound command.
This problem is most commonly observed with ‘{...}’; it does not
occur with ‘(...)’. For example:
$ bash -c '{ echo foo; } >/bad; echo $?'
bash: line 1: /bad: Permission denied
0
$ bash -c 'while :; do echo; done >/bad; echo $?'
bash: line 1: /bad: Permission denied
0
To work around the bug, prepend ‘:;’:
$ bash -c ':;{ echo foo; } >/bad; echo $?'
bash: line 1: /bad: Permission denied
1
Posix requires a syntax error if a brace list has no contents.
However, not all shells obey this rule; and on shells where empty
lists are permitted, the effect on ‘$?’ is inconsistent. To avoid
problems, ensure that a brace list is never empty.
$ bash -c 'false; { }; echo $?' || echo $?
bash: line 1: syntax error near unexpected token `}'
bash: line 1: `false; { }; echo $?'
2
$ zsh -c 'false; { }; echo $?' || echo $?
1
$ pdksh -c 'false; { }; echo $?' || echo $?
0
‘break’
The use of ‘break 2’ etc. is safe.
‘case’
If a ‘case’ command is not inside ‘AC_DEFUN’, and it contains calls
to Autoconf macros, it should be rewritten using ‘AS_CASE’. *Note
Common Shell Constructs::. Instead of:
# This is dangerous outside AC_DEFUN.
case $filename in
*.[ch]) AC_MSG_NOTICE([C source file]);;
esac
use:
# This is OK outside AC_DEFUN.
AS_CASE([$filename],
[[*.[ch]]], [AC_MSG_NOTICE([C source file])])
You don't need to quote the argument; no splitting is performed.
You don't need the final ‘;;’, but you should use it.
Posix requires support for ‘case’ patterns with opening parentheses
like this:
case $file_name in
(*.c) echo "C source code";;
esac
but the ‘(’ in this example is not portable to a few obsolescent
Bourne shell implementations, which is a pity for those of us using
tools that rely on balanced parentheses. For instance, with
Solaris 10 ‘/bin/sh’:
$ case foo in (foo) echo foo;; esac
error→syntax error: `(' unexpected
The leading ‘(’ can be omitted safely. Unfortunately, there are
contexts where unbalanced parentheses cause other problems, such as
when using a syntax-highlighting editor that searches for the
balancing counterpart, or more importantly, when using a case
statement as an underquoted argument to an Autoconf macro. *Note
Balancing Parentheses::, for trade-offs involved in various styles
of dealing with unbalanced ‘)’.
Zsh handles pattern fragments derived from parameter expansions or
command substitutions as though quoted:
$ pat=\?; case aa in ?$pat) echo match;; esac
$ pat=\?; case a? in ?$pat) echo match;; esac
match
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 Posix or MS-DOS absolute file names. 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
Many Bourne shells cannot handle closing brackets in character
classes correctly.
Some shells also have problems with backslash escaping in case you
do not want to match the backslash: both a backslash and the
escaped character match this pattern. To work around this, specify
the character class in a variable, so that quote removal does not
apply afterwards, and the special characters don't have to be
backslash-escaped:
$ case '\' in [\<]) echo OK;; esac
OK
$ scanset='[<]'; case '\' in $scanset) echo OK;; esac
$
Even with this, Solaris ‘ksh’ matches a backslash if the set
contains any of the characters ‘|’, ‘&’, ‘(’, or ‘)’.
Conversely, Tru64 ‘ksh’ (circa 2003) erroneously always matches a
closing parenthesis if not specified in a character class:
$ case foo in *\)*) echo fail ;; esac
fail
$ case foo in *')'*) echo fail ;; esac
fail
Some shells, such as Ash 0.3.8, are confused by an empty
‘case’/‘esac’:
ash-0.3.8 $ case foo in esac;
error→Syntax error: ";" unexpected (expecting ")")
Posix requires ‘case’ to give an exit status of 0 if no cases
match. However, ‘/bin/sh’ in Solaris 10 does not obey this rule.
Meanwhile, it is unclear whether a case that matches, but contains
no statements, must also change the exit status to 0. The M4sh
macro ‘AS_CASE’ works around these inconsistencies.
$ bash -c 'case `false` in ?) ;; esac; echo $?'
0
$ /bin/sh -c 'case `false` in ?) ;; esac; echo $?'
255
‘cd’
Posix 1003.1-2001 requires that ‘cd’ must support the ‘-L’
("logical") and ‘-P’ ("physical") options, with ‘-L’ being the
default. However, traditional shells do not support these options,
and their ‘cd’ command has the ‘-P’ behavior.
Portable scripts should assume neither option is supported, and
should assume neither behavior is the default. This can be a bit
tricky, since the Posix default behavior means that, for example,
‘ls ..’ and ‘cd ..’ may refer to different directories if the
current logical directory is a symbolic link. It is safe to use
‘cd DIR’ if DIR contains no ‘..’ components. Also,
Autoconf-generated scripts check for this problem when computing
variables like ‘ac_top_srcdir’ (*note Configuration Actions::), so
it is safe to ‘cd’ to these variables.
Posix states that behavior is undefined if ‘cd’ is given an
explicit empty argument. Some shells do nothing, some change to
the first entry in ‘CDPATH’, some change to ‘HOME’, and some exit
the shell rather than returning an error. Unfortunately, this
means that if ‘$var’ is empty, then ‘cd "$var"’ is less predictable
than ‘cd $var’ (at least the latter is well-behaved in all shells
at changing to ‘HOME’, although this is probably not what you
wanted in a script). You should check that a directory name was
supplied before trying to change locations.
*Note Special Shell Variables::, for portability problems involving
‘cd’ and the ‘CDPATH’ environment variable. Also please see the
discussion of the ‘pwd’ command.
‘echo’
The simple ‘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. Don't expect
any option.
Do not use backslashes in the arguments, as there is no consensus
on their handling. For ‘echo '\n' | wc -l’, the ‘sh’ of Solaris 10
outputs 2, but Bash and Zsh (in ‘sh’ emulation mode) output 1. The
problem is truly ‘echo’: all the shells understand ‘'\n'’ as the
string composed of a backslash and an ‘n’. Within a command
substitution, ‘echo 'string\c'’ will mess up the internal state of
ksh88 on AIX 6.1 so that it will print the first character ‘s’
only, followed by a newline, and then entirely drop the output of
the next echo in a command substitution.
Because of these problems, do not pass a string containing
arbitrary characters to ‘echo’. For example, ‘echo "$foo"’ is safe
only if you know that FOO's value cannot contain backslashes and
cannot start with ‘-’.
Normally, ‘printf’ is safer and easier to use than ‘echo’ and ‘echo
-n’. Thus, you should use ‘printf "%s\n"’ instead of ‘echo’, and
similarly use ‘printf %s’ instead of ‘echo -n’.
Older scripts, written before ‘printf’ was portable, sometimes used
a here-document as a safer alternative to ‘echo’, like this:
cat <bar'’, since it
mistakenly replaces the contents of ‘bar’ by the string ‘cat foo’.
No simple, general, and portable solution to this problem is known.
‘exec’
Posix describes several categories of shell built-ins. Special
built-ins (such as ‘exit’) must impact the environment of the
current shell, and need not be available through ‘exec’. All other
built-ins are regular, and must not propagate variable assignments
to the environment of the current shell. However, the group of
regular built-ins is further distinguished by commands that do not
require a ‘PATH’ search (such as ‘cd’), in contrast to built-ins
that are offered as a more efficient version of something that must
still be found in a ‘PATH’ search (such as ‘echo’). Posix is not
clear on whether ‘exec’ must work with the list of 17 utilities
that are invoked without a ‘PATH’ search, and many platforms lack
an executable for some of those built-ins:
$ sh -c 'exec cd /tmp'
sh: line 0: exec: cd: not found
All other built-ins that provide utilities specified by Posix must
have a counterpart executable that exists on ‘PATH’, although Posix
allows ‘exec’ to use the built-in instead of the executable. For
example, contrast ‘bash’ 3.2 and ‘pdksh’ 5.2.14:
$ bash -c 'pwd --version' | head -n1
bash: line 0: pwd: --: invalid option
pwd: usage: pwd [-LP]
$ bash -c 'exec pwd --version' | head -n1
pwd (GNU coreutils) 6.10
$ pdksh -c 'exec pwd --version' | head -n1
pdksh: pwd: --: unknown option
When it is desired to avoid a regular shell built-in, the
workaround is to use some other forwarding command, such as ‘env’
or ‘nice’, that will ensure a path search:
$ pdksh -c 'exec true --version' | head -n1
$ pdksh -c 'nice true --version' | head -n1
true (GNU coreutils) 6.10
$ pdksh -c 'env true --version' | head -n1
true (GNU coreutils) 6.10
‘exit’
The default value of ‘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 10 ‘/bin/sh’, an
exit trap ignores the ‘exit’ command's argument. 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’
The builtin ‘export’ dubs a shell variable “environment 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 10 ‘/bin/sh’, IRIX 6.3, IRIX
5.2, AIX 4.1.5, and Digital Unix 4.0, forget to ‘export’ the
environment variables they receive. As a result, two variables
coexist: 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 print
alternately ‘foo’ and ‘bar’, although they should print only ‘foo’
and then a sequence of ‘bar’s.
Therefore you should ‘export’ again each environment variable that
you update; the export can occur before or after the assignment.
Posix is not clear on whether the ‘export’ of an undefined variable
causes the variable to be defined with the value of an empty
string, or merely marks any future definition of a variable by that
name for export. Various shells behave differently in this regard:
$ sh -c 'export foo; env | grep foo'
$ ash -c 'export foo; env | grep foo'
foo=
Posix requires ‘export’ to honor assignments made as arguments, but
older shells do not support this, including ‘/bin/sh’ in Solaris
10. Portable scripts should separate assignments and exports into
different statements.
$ bash -c 'export foo=bar; echo $foo'
bar
$ /bin/sh -c 'export foo=bar; echo $foo'
/bin/sh: foo=bar: is not an identifier
$ /bin/sh -c 'export foo; foo=bar; echo $foo'
bar
Posix requires ‘export’ to work with any arbitrary value for the
contents of the variable being exported, as long as the total size
of the environment combined with arguments doesn't exceed ‘ARG_MAX’
when executing a child process. However, some shells have
extensions that involve interpreting some environment values
specially, regardless of the variable name. We currently know of
one case: all versions of Bash released prior to 27 September 2014
interpret an environment variable with an initial content substring
of ‘() {’ as an exported function definition (this is the
"Shellshock" remote execution bug, CVE-2014-6271 and friends, where
it was possible to exploit the function parser to cause remote code
execution on child bash startup; newer versions of Bash use special
environment variable _names_ instead of values to implement the
same feature).
There may be entries inherited into the environment that are not
valid as shell variable names; Posix states that processes should
be tolerant of these names. Some shells such as ‘dash’ do this by
removing those names from the environment at startup, while others
such as ‘bash’ hide the entry from shell access but still pass it
on to child processes. While you can set such names using ‘env’
for a direct child process, you cannot rely on them being preserved
through an intermediate pass through the shell.
‘false’
Don't expect ‘false’ to exit with status 1: in native Solaris
‘/bin/false’ exits with status 255.
‘for’
To loop over positional arguments, use:
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 (*note Shell Substitutions::), use:
for arg in ${1+"$@"}; do
echo "$arg"
done
But keep in mind that Zsh, even in Bourne shell emulation mode,
performs word splitting on ‘${1+"$@"}’; see *note Shell
Substitutions::, item ‘$@’, for more.
Posix requires support for a ‘for’ loop with no list after ‘in’.
However, Solaris 10 ‘/bin/sh’ treats that as a syntax error. It is
possible to work around this by providing any shell word that
expands to nothing, or by ignoring an obvious sentinel.
$ /bin/sh -c 'for a in $empty; do echo hi; done'
$ /bin/sh -c 'for a in ; do echo hi; done'
/bin/sh: syntax error at line 1: `;' unexpected
This syntax problem is most frequently encountered in code that
goes through several layers of expansion, such as an m4 macro or
makefile variable used as a list body, where the first layer of
expansion (m4 or make) can end up expanding to nothing in the
version handed to the shell. In the makefile context, one common
workaround is to use a shell variable rather than a make variable
as the source of the list.
$ cat Makefile
list =
bad:
@for arg in $(list); do echo $$arg; done
good:
@list='$(list)'; for arg in $$list; do echo $$arg; done
$ make bad 2&>1 | head -n1
sh: syntax error at line 1: `;' unexpected
$ make bad list='a b'
a
b
$ make good
$ make good list='a b'
a
b
In Solaris 10 ‘/bin/sh’, when the list of arguments of a ‘for’ loop
starts with _unquoted_ tokens looking like variable assignments,
the loop is not executed on those tokens:
$ /bin/sh -c 'for v in a=b c=d x e=f; do echo $v; done'
x
e=f
Thankfully, quoting the assignment-like tokens, or starting the
list with other tokens (including unquoted variable expansion that
results in an assignment-like result), avoids the problem, so it is
easy to work around:
$ /bin/sh -c 'for v in "a=b"; do echo $v; done'
a=b
$ /bin/sh -c 'x=a=b; for v in $x c=d; do echo $v; done'
a=b
c=d
‘if’
If an ‘if’ command is not inside ‘AC_DEFUN’, and it contains calls
to Autoconf macros, it should be rewritten using ‘AS_IF’. *Note
Common Shell Constructs::.
Using ‘if ! ...’ is not portable. *Note ‘!’ notes: !.
Some very old shells did not reset the exit status from an ‘if’
with no ‘else’:
$ if (exit 42); then true; fi; echo $?
42
whereas a proper shell should have printed ‘0’. Although this is
no longer a portability problem, as any shell that supports
functions gets it correct, it explains why some makefiles have
lengthy constructs:
if test -f "$file"; then
install "$file" "$dest"
else
:
fi
‘printf’
A format string starting with a ‘-’ can cause problems. Bash
interprets it as an option and gives an error. And ‘--’ to mark
the end of options is not good in the NetBSD Almquist shell (e.g.,
0.4.6) which takes that literally as the format string. Putting
the ‘-’ in a ‘%c’ or ‘%s’ is probably easiest:
printf %s -foo
AIX 7.2 ‘sh’ mishandles octal escapes in multi-byte locales by
treating them as characters instead of bytes. For example, in a
locale using the UTF-8 encoding, ‘printf '\351'’ outputs the two
bytes C3, A9 (the UTF-8 encoding for U+00E9) instead of the desired
single byte E9. To work around the bug, use the C locale.
Bash 2.03 mishandles an escape sequence that happens to evaluate to
‘%’:
$ printf '\045'
bash: printf: `%': missing format character
Large outputs may cause trouble. On Solaris 10, for example,
‘/usr/bin/printf’ is buggy, so when using ‘/bin/sh’ the command
‘printf %010000x 123’ normally dumps core.
Since ‘printf’ is not always a shell builtin, there is a potential
speed penalty for using ‘printf '%s\n'’ as a replacement for an
‘echo’ that does not interpret ‘\’ or leading ‘-’. With Solaris
‘ksh’, it is possible to use ‘print -r --’ for this role instead.
*Note Limitations of Shell Builtins: echo, for a discussion of
portable alternatives to both ‘printf’ and ‘echo’.
‘pwd’
With modern shells, plain ‘pwd’ outputs a "logical" directory name,
some of whose components may be symbolic links. These directory
names are in contrast to "physical" directory names, whose
components are all directories.
Posix 1003.1-2001 requires that ‘pwd’ must support the ‘-L’
("logical") and ‘-P’ ("physical") options, with ‘-L’ being the
default. However, traditional shells do not support these options,
and their ‘pwd’ command has the ‘-P’ behavior.
Portable scripts should assume neither option is supported, and
should assume neither behavior is the default. Also, on many hosts
‘/bin/pwd’ is equivalent to ‘pwd -P’, but Posix does not require
this behavior and portable scripts should not rely on it.
Typically it's best to use plain ‘pwd’. On modern hosts this
outputs logical directory names, which have the following
advantages:
• Logical names are what the user specified.
• Physical names may not be portable from one installation host
to another due to network file system gymnastics.
• On modern hosts ‘pwd -P’ may fail due to lack of permissions
to some parent directory, but plain ‘pwd’ cannot fail for this
reason.
Also please see the discussion of the ‘cd’ command.
‘read’
No options are portable, not even support ‘-r’ (Solaris 10
‘/bin/sh’ for example). Tru64/OSF 5.1 ‘sh’ treats ‘read’ as a
special built-in, so it may exit if input is redirected from a
non-existent or unreadable file.
‘set’
With the FreeBSD 6.0 shell, the ‘set’ command (without any options)
does not sort its output.
The ‘set’ builtin faces the usual problem with arguments starting
with a dash. Modern shells such as Bash or Zsh understand ‘--’ to
specify the end of the options (any argument after ‘--’ is a
parameter, even ‘-x’ for instance), but many traditional shells
(e.g., Solaris 10 ‘/bin/sh’) simply stop 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
Avoid ‘set -’, e.g., ‘set - $my_list’. Posix no longer requires
support for this command, and in traditional shells ‘set -
$my_list’ resets the ‘-v’ and ‘-x’ options, which makes scripts
harder to debug.
Some nonstandard shells do not recognize more than one option
(e.g., ‘set -e -x’ assigns ‘-x’ to the command line). It is better
to combine them:
set -ex
The ‘-e’ option has historically been under-specified, with enough
ambiguities to cause numerous differences across various shell
implementations; see for example this overview
(https://www.in-ulm.de/~mascheck/various/set-e/), or this link
(https://www.austingroupbugs.net/view.php?id=52), documenting a
change to Posix 2008 to match ‘ksh88’ behavior. Note that mixing
‘set -e’ and shell functions is asking for surprises:
set -e
doit()
{
rm file
echo one
}
doit || echo two
According to the recommendation, ‘one’ should always be output
regardless of whether the ‘rm’ failed, because it occurs within the
body of the shell function ‘doit’ invoked on the left side of ‘||’,
where the effects of ‘set -e’ are not enforced. Likewise, ‘two’
should never be printed, since the failure of ‘rm’ does not abort
the function, such that the status of ‘doit’ is 0.
The BSD shell has had several problems with the ‘-e’ option. Older
versions of the BSD shell (circa 1990) mishandled ‘&&’, ‘||’, ‘if’,
and ‘case’ when ‘-e’ was in effect, causing the shell to exit
unexpectedly in some cases. This was particularly a problem with
makefiles, and led to circumlocutions like ‘sh -c 'test -f file ||
touch file'’, where the seemingly-unnecessary ‘sh -c '...'’ wrapper
works around the bug (*note Failure in Make Rules::).
Even relatively-recent versions of the BSD shell (e.g., OpenBSD
3.4) wrongly exit with ‘-e’ if the last command within a compound
statement fails and is guarded by an ‘&&’ only. For example:
#! /bin/sh
set -e
foo=''
test -n "$foo" && exit 1
echo one
if :; then
test -n "$foo" && exit 1
echo two
test -n "$foo" && exit 1
fi
echo three
does not print ‘three’. One workaround is to change the last
instance of ‘test -n "$foo" && exit 1’ to be ‘if test -n "$foo";
then exit 1; fi’ instead. Another possibility is to warn BSD users
not to use ‘sh -e’.
When ‘set -e’ is in effect, a failed command substitution in
Solaris 10 ‘/bin/sh’ cannot be ignored, even with ‘||’.
$ /bin/sh -c 'set -e; foo=`false` || echo foo; echo bar'
$ bash -c 'set -e; foo=`false` || echo foo; echo bar'
foo
bar
Moreover, a command substitution, successful or not, causes this
shell to exit from a failing outer command even in presence of an
‘&&’ list:
$ bash -c 'set -e; false `true` && echo notreached; echo ok'
ok
$ sh -c 'set -e; false `true` && echo notreached; echo ok'
$
Portable scripts should not use ‘set -e’ if ‘trap’ is used to
install an exit handler. This is because Tru64/OSF 5.1 ‘sh’
sometimes enters the trap handler with the exit status of the
command prior to the one that triggered the errexit handler:
$ sh -ec 'trap '\''echo $?'\'' 0; false'
0
$ sh -c 'set -e; trap '\''echo $?'\'' 0; false'
1
Thus, when writing a script in M4sh, rather than trying to rely on
‘set -e’, it is better to use ‘AS_EXIT’ where it is desirable to
abort on failure.
Job control is not provided by all shells, so the use of ‘set -m’
or ‘set -b’ must be done with care. When using ‘zsh’ in native
mode, asynchronous notification (‘set -b’) is enabled by default,
and using ‘emulate sh’ to switch to Posix mode does not clear this
setting (although asynchronous notification has no impact unless
job monitoring is also enabled). Also, ‘zsh’ 4.3.10 and earlier
have a bug where job control can be manipulated in interactive
shells, but not in subshells or scripts. Furthermore, some shells,
like ‘pdksh’, fail to treat subshells as interactive, even though
the parent shell was.
$ echo $ZSH_VERSION
4.3.10
$ set -m; echo $?
0
$ zsh -c 'set -m; echo $?'
set: can't change option: -m
$ (set -m); echo $?
set: can't change option: -m
1
$ pdksh -ci 'echo $-; (echo $-)'
cim
c
Use of ‘set -n’ (typically via ‘sh -n script’) to validate a script
is not foolproof. Modern ‘ksh93’ tries to be helpful by informing
you about better syntax, but switching the script to use the
suggested syntax in order to silence the warnings would render the
script no longer portable to older shells:
$ ksh -nc '``'
ksh: warning: line 1: `...` obsolete, use $(...)
0
Autoconf itself uses ‘sh -n’ within its testsuite to check that
correct scripts were generated, but only after first probing for
other shell features (such as ‘test ${BASH_VERSION+y}’) that
indicate a reasonably fast and working implementation.
‘shift’
Not only is ‘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.
Don't use ‘shift 2’ etc.; while it in the SVR1 shell (1983), it is
also absent in many pre-Posix shells.
‘source’
This command is not portable, as Posix does not require it; use ‘.’
instead.
‘test’
The ‘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.
The ‘-a’, ‘-o’, ‘(’, and ‘)’ operands are not present in all
implementations, and have been marked obsolete by Posix 2008. This
is because there are inherent ambiguities in using them. For
example, ‘test "$1" -a "$2"’ looks like a binary operator to check
whether two strings are both non-empty, but if ‘$1’ is the literal
‘!’, then some implementations of ‘test’ treat it as a negation of
the unary operator ‘-a’.
Thus, portable uses of ‘test’ should never have more than four
arguments, and scripts should use shell constructs like ‘&&’ and
‘||’ instead. If you combine ‘&&’ and ‘||’ in the same statement,
keep in mind that they have equal precedence, so it is often better
to parenthesize even when this is redundant. For example:
# Not portable:
test "X$a" = "X$b" -a \
'(' "X$c" != "X$d" -o "X$e" = "X$f" ')'
# Portable:
test "X$a" = "X$b" &&
{ test "X$c" != "X$d" || test "X$e" = "X$f"; }
‘test’ does not process options like most other commands do; for
example, it does not recognize the ‘--’ argument as marking the end
of options.
It is safe to use ‘!’ as a ‘test’ operator. For example, ‘if test
! -d foo; ...’ is portable even though ‘if ! test -d foo; ...’ is
not.
‘test’ (files)
To enable ‘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’, ‘test -r’, or ‘test -x’. Do not use ‘test -e’,
because Solaris 10 ‘/bin/sh’ lacks it. To test for symbolic links
on systems that have them, use ‘test -h’ rather than ‘test -L’;
either form conforms to Posix 1003.1-2001, but ‘-h’ has been around
longer.
For historical reasons, Posix reluctantly allows implementations of
‘test -x’ that will succeed for the root user, even if no execute
permissions are present. Furthermore, shells do not all agree on
whether Access Control Lists should affect ‘test -r’, ‘test -w’,
and ‘test -x’; some shells base test results strictly on the
current user id compared to file owner and mode, as if by
‘stat(2)’; while other shells base test results on whether the
current user has the given right, even if that right is only
granted by an ACL, as if by ‘faccessat(2)’. Furthermore, there is
a classic time of check to time of use race between any use of
‘test’ followed by operating on the just-checked file. Therefore,
it is a good idea to write scripts that actually attempt an
operation, and are prepared for the resulting failure if permission
is denied, rather than trying to avoid an operation based solely on
whether ‘test’ guessed that it might not be permitted.
‘test’ (strings)
Posix says that ‘test "STRING"’ succeeds if STRING is not null, but
this usage is not portable to traditional platforms like Solaris 10
‘/bin/sh’, which mishandle strings like ‘!’ and ‘-n’. However, it
_is_ portable to test if a variable is set to a non-empty value, by
using ‘test ${var+y}’, since all known implementations properly
distinguish between no arguments and a known-safe string of ‘y’.
Posix also says that ‘test ! "STRING"’, ‘test -n "STRING"’ and
‘test -z "STRING"’ work with any string, but many shells (such as
Solaris 10, AIX 3.2, UNICOS 10.0.0.6, Digital Unix 4, etc.) get
confused if STRING looks like an operator:
$ test -n =
test: argument expected
$ test ! -n
test: argument expected
$ test -z ")"; echo $?
0
Similarly, Posix says that both ‘test "STRING1" = "STRING2"’ and
‘test "STRING1" != "STRING2"’ work for any pairs of strings, but in
practice this is not true for troublesome strings that look like
operators or parentheses, or that begin with ‘-’.
It is best to protect such strings with a leading ‘X’, e.g., ‘test
"XSTRING" != X’ rather than ‘test -n "STRING"’ or ‘test !
"STRING"’.
It is common 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 be avoided by using:
AS_CASE([$ac_feature],
[[*[!-a-zA-Z0-9_]*]], [ACTION])
If the pattern is a complicated regular expression that cannot be
expressed as a shell pattern, use something like this instead:
expr "X$ac_feature" : 'X.*[^-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’
It is safe to trap at least the signals 1, 2, 13, and 15. You can
also trap 0, i.e., have the ‘trap’ run when the script ends (either
via an explicit ‘exit’, or the end of the script). The trap for 0
should be installed outside of a shell function, or AIX 5.3
‘/bin/sh’ will invoke the trap at the end of this function.
Posix says that ‘trap - 1 2 13 15’ resets the traps for the
specified signals to their default values, but many common shells
(e.g., Solaris 10 ‘/bin/sh’) misinterpret this and attempt to
execute a "command" named ‘-’ when the specified conditions arise.
Posix 2008 also added a requirement to support ‘trap 1 2 13 15’ to
reset traps, as this is supported by a larger set of shells, but
there are still shells like ‘dash’ that mistakenly try to execute
‘1’ instead of resetting the traps. Therefore, there is no
portable workaround, except for ‘trap - 0’, for which ‘trap '' 0’
is a portable substitute.
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
10 ‘/bin/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. In M4sh, this is covered by using
‘AS_EXIT’.
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’.
Several shells fail to execute an exit trap that is defined inside
a subshell, when the last command of that subshell is not a
builtin. A workaround is to use ‘exit $?’ as the shell builtin.
$ bash -c '(trap "echo hi" 0; /bin/true)'
hi
$ /bin/sh -c '(trap "echo hi" 0; /bin/true)'
$ /bin/sh -c '(trap "echo hi" 0; /bin/true; exit $?)'
hi
Likewise, older implementations of ‘bash’ failed to preserve ‘$?’
across an exit trap consisting of a single cleanup command.
$ bash -c 'trap "/bin/true" 0; exit 2'; echo $?
2
$ bash-2.05b -c 'trap "/bin/true" 0; exit 2'; echo $?
0
$ bash-2.05b -c 'trap ":; /bin/true" 0; exit 2'; echo $?
2
Be aware that a trap can be called from any number of places in
your script, and therefore the trap handler should not make
assumptions about shell state. For some examples, if your script
temporarily modifies ‘IFS’, then the trap should include an
initialization back to its typical value of space-tab-newline
(autoconf does this for generated ‘configure’ files). Likewise, if
your script changes the current working directory at some point
after the trap is installed, then your trap cannot assume which
directory it is in, and should begin by changing directories to an
absolute path if that is important to the cleanup efforts (autotest
does this for generated ‘testsuite’ files).
‘true’
Don't worry: as far as we know ‘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 for
‘false’, but not for ‘true’.
Remember that even though ‘:’ ignores its arguments, it still takes
time to compute those arguments. It is a good idea to use double
quotes around any arguments to ‘:’ to avoid time spent in field
splitting and file name expansion.
‘unset’
In some nonconforming shells (e.g., Solaris 10 ‘/bin/ksh’ and
‘/usr/xpg4/bin/sh’, NetBSD 5.99.43 sh, or Bash 2.05a), ‘unset FOO’
fails when ‘FOO’ is not set. This can interfere with ‘set -e’
operation. You can use
FOO=; unset FOO
if you are not sure that ‘FOO’ is set.
A few ancient shells lack ‘unset’ entirely. For some variables
such as ‘PS1’, you can use a neutralizing value instead:
PS1='$ '
Usually, shells that do not support ‘unset’ need less effort to
make the environment sane, so for example is not a problem if you
cannot unset ‘CDPATH’ on those shells. However, Bash 2.01
mishandles ‘unset MAIL’ and ‘unset MAILPATH’ in some cases and
dumps core. So, you should do something like
( (unset MAIL) || exit 1) >/dev/null 2>&1 && unset MAIL || :
*Note Special Shell Variables::, for some neutralizing values.
Also, see *note Limitations of Builtins: export, for the case of
environment variables.
‘wait’
The exit status of ‘wait’ is not always reliable.