autoconf: Limitations of Builtins
1
1 11.14 Limitations of Shell Builtins
1 ===================================
1
1 No, no, we are serious: some shells do have limitations! :)
1
1 You should always keep in mind that any builtin or command may
1 support options, and therefore differ in behavior with arguments
1 starting with a dash. For instance, even the innocent `echo "$word"'
1 can give unexpected results when `word' starts with a dash. It is
1 often possible to avoid this problem using `echo "x$word"', taking the
1 `x' into account later in the pipe. Many of these limitations can be
1 worked around using M4sh (⇒Programming in M4sh).
1
1 `.'
1 Use `.' only with regular files (use `test -f'). Bash 2.03, for
1 instance, chokes on `. /dev/null'. Remember that `.' uses `PATH'
1 if its argument contains no slashes. Also, some shells, including
1 bash 3.2, implicitly append the current directory to this `PATH'
1 search, even though Posix forbids it. So if you want to use `.'
1 on a file `foo' in the current directory, you must use `. ./foo'.
1
1 Not all shells gracefully handle syntax errors within a sourced
1 file. On one extreme, some non-interactive shells abort the
1 entire script. On the other, `zsh' 4.3.10 has a bug where it
1 fails to react to the syntax error.
1
1 $ echo 'fi' > syntax
1 $ bash -c '. ./syntax; echo $?'
1 ./syntax: line 1: syntax error near unexpected token `fi'
1 ./syntax: line 1: `fi'
1 1
1 $ ash -c '. ./syntax; echo $?'
1 ./syntax: 1: Syntax error: "fi" unexpected
1 $ zsh -c '. ./syntax; echo $?'
1 ./syntax:1: parse error near `fi'
1 0
1
1 `!'
1 The Unix version 7 shell did not support negating the exit status
1 of commands with `!', and this feature is still absent from some
1 shells (e.g., Solaris `/bin/sh'). Other shells, such as FreeBSD
1 `/bin/sh' or `ash', have bugs when using `!':
1
1 $ sh -c '! : | :'; echo $?
1 1
1 $ ash -c '! : | :'; echo $?
1 0
1 $ sh -c '! { :; }'; echo $?
1 1
1 $ ash -c '! { :; }'; echo $?
1 {: not found
1 Syntax error: "}" unexpected
1 2
1
1 Shell code like this:
1
1 if ! cmp file1 file2 >/dev/null 2>&1; then
1 echo files differ or trouble
1 fi
1
1 is therefore not portable in practice. Typically it is easy to
1 rewrite such code, e.g.:
1
1 cmp file1 file2 >/dev/null 2>&1 ||
1 echo files differ or trouble
1
1 More generally, one can always rewrite `! COMMAND' as:
1
1 if COMMAND; then (exit 1); else :; fi
1
1 `{...}'
1 Bash 3.2 (and earlier versions) sometimes does not properly set
1 `$?' when failing to write redirected output of a compound command.
1 This problem is most commonly observed with `{...}'; it does not
1 occur with `(...)'. For example:
1
1 $ bash -c '{ echo foo; } >/bad; echo $?'
1 bash: line 1: /bad: Permission denied
1 0
1 $ bash -c 'while :; do echo; done >/bad; echo $?'
1 bash: line 1: /bad: Permission denied
1 0
1
1 To work around the bug, prepend `:;':
1
1 $ bash -c ':;{ echo foo; } >/bad; echo $?'
1 bash: line 1: /bad: Permission denied
1 1
1
1 Posix requires a syntax error if a brace list has no contents.
1 However, not all shells obey this rule; and on shells where empty
1 lists are permitted, the effect on `$?' is inconsistent. To avoid
1 problems, ensure that a brace list is never empty.
1
1 $ bash -c 'false; { }; echo $?' || echo $?
1 bash: line 1: syntax error near unexpected token `}'
1 bash: line 1: `false; { }; echo $?'
1 2
1 $ zsh -c 'false; { }; echo $?' || echo $?
1 1
1 $ pdksh -c 'false; { }; echo $?' || echo $?
1 0
1
1 `break'
1 The use of `break 2' etc. is safe.
1
1 `case'
1 You don't need to quote the argument; no splitting is performed.
1
1 You don't need the final `;;', but you should use it.
1
1 Posix requires support for `case' patterns with opening
1 parentheses like this:
1
1 case $file_name in
1 (*.c) echo "C source code";;
1 esac
1
1 but the `(' in this example is not portable to many Bourne shell
1 implementations, which is a pity for those of us using tools that
1 rely on balanced parentheses. For instance, with Solaris
1 `/bin/sh':
1
1 $ case foo in (foo) echo foo;; esac
1 error-->syntax error: `(' unexpected
1
1 The leading `(' can be omitted safely. Unfortunately, there are
1 contexts where unbalanced parentheses cause other problems, such
1 as when using a syntax-highlighting editor that searches for the
1 balancing counterpart, or more importantly, when using a case
11 statement as an underquoted argument to an Autoconf macro. ⇒
Balancing Parentheses, for tradeoffs involved in various styles
1 of dealing with unbalanced `)'.
1
1 Zsh handles pattern fragments derived from parameter expansions or
1 command substitutions as though quoted:
1
1 $ pat=\?; case aa in ?$pat) echo match;; esac
1 $ pat=\?; case a? in ?$pat) echo match;; esac
1 match
1
1 Because of a bug in its `fnmatch', Bash fails to properly handle
1 backslashes in character classes:
1
1 bash-2.02$ case /tmp in [/\\]*) echo OK;; esac
1 bash-2.02$
1
1 This is extremely unfortunate, since you are likely to use this
1 code to handle Posix or MS-DOS absolute file names. To work
1 around this bug, always put the backslash first:
1
1 bash-2.02$ case '\TMP' in [\\/]*) echo OK;; esac
1 OK
1 bash-2.02$ case /tmp in [\\/]*) echo OK;; esac
1 OK
1
1 Many Bourne shells cannot handle closing brackets in character
1 classes correctly.
1
1 Some shells also have problems with backslash escaping in case you
1 do not want to match the backslash: both a backslash and the
1 escaped character match this pattern. To work around this,
1 specify the character class in a variable, so that quote removal
1 does not apply afterwards, and the special characters don't have
1 to be backslash-escaped:
1
1 $ case '\' in [\<]) echo OK;; esac
1 OK
1 $ scanset='[<]'; case '\' in $scanset) echo OK;; esac
1 $
1
1 Even with this, Solaris `ksh' matches a backslash if the set
1 contains any of the characters `|', `&', `(', or `)'.
1
1 Conversely, Tru64 `ksh' (circa 2003) erroneously always matches a
1 closing parenthesis if not specified in a character class:
1
1 $ case foo in *\)*) echo fail ;; esac
1 fail
1 $ case foo in *')'*) echo fail ;; esac
1 fail
1
1 Some shells, such as Ash 0.3.8, are confused by an empty
1 `case'/`esac':
1
1 ash-0.3.8 $ case foo in esac;
1 error-->Syntax error: ";" unexpected (expecting ")")
1
1 Posix requires `case' to give an exit status of 0 if no cases
1 match. However, `/bin/sh' in Solaris 10 does not obey this rule.
1 Meanwhile, it is unclear whether a case that matches, but contains
1 no statements, must also change the exit status to 0. The M4sh
1 macro `AS_CASE' works around these inconsistencies.
1
1 $ bash -c 'case `false` in ?) ;; esac; echo $?'
1 0
1 $ /bin/sh -c 'case `false` in ?) ;; esac; echo $?'
1 255
1
1 `cd'
1 Posix 1003.1-2001 requires that `cd' must support the `-L'
1 ("logical") and `-P' ("physical") options, with `-L' being the
1 default. However, traditional shells do not support these
1 options, and their `cd' command has the `-P' behavior.
1
1 Portable scripts should assume neither option is supported, and
1 should assume neither behavior is the default. This can be a bit
1 tricky, since the Posix default behavior means that, for example,
1 `ls ..' and `cd ..' may refer to different directories if the
1 current logical directory is a symbolic link. It is safe to use
1 `cd DIR' if DIR contains no `..' components. Also,
1 Autoconf-generated scripts check for this problem when computing
1 variables like `ac_top_srcdir' (⇒Configuration Actions), so
1 it is safe to `cd' to these variables.
1
1 Posix states that behavior is undefined if `cd' is given an
1 explicit empty argument. Some shells do nothing, some change to
1 the first entry in `CDPATH', some change to `HOME', and some exit
1 the shell rather than returning an error. Unfortunately, this
1 means that if `$var' is empty, then `cd "$var"' is less predictable
1 than `cd $var' (at least the latter is well-behaved in all shells
1 at changing to `HOME', although this is probably not what you
1 wanted in a script). You should check that a directory name was
1 supplied before trying to change locations.
1
1 ⇒Special Shell Variables, for portability problems involving
1 `cd' and the `CDPATH' environment variable. Also please see the
1 discussion of the `pwd' command.
1
1 `echo'
1 The simple `echo' is probably the most surprising source of
1 portability troubles. It is not possible to use `echo' portably
1 unless both options and escape sequences are omitted. Don't
1 expect any option.
1
1 Do not use backslashes in the arguments, as there is no consensus
1 on their handling. For `echo '\n' | wc -l', the `sh' of Solaris
1 outputs 2, but Bash and Zsh (in `sh' emulation mode) output 1.
1 The problem is truly `echo': all the shells understand `'\n'' as
1 the string composed of a backslash and an `n'. Within a command
1 substitution, `echo 'string\c'' will mess up the internal state of
1 ksh88 on AIX 6.1 so that it will print the first character `s'
1 only, followed by a newline, and then entirely drop the output of
1 the next echo in a command substitution.
1
1 Because of these problems, do not pass a string containing
1 arbitrary characters to `echo'. For example, `echo "$foo"' is safe
1 only if you know that FOO's value cannot contain backslashes and
1 cannot start with `-'.
1
1 If this may not be true, `printf' is in general safer and easier
1 to use than `echo' and `echo -n'. Thus, scripts where portability
1 is not a major concern should use `printf '%s\n'' whenever `echo'
1 could fail, and similarly use `printf %s' instead of `echo -n'.
1 For portable shell scripts, instead, it is suggested to use a
1 here-document like this:
1
1 cat <<EOF
1 $foo
1 EOF
1
1 Alternatively, M4sh provides `AS_ECHO' and `AS_ECHO_N' macros
1 which choose between various portable implementations: `echo' or
1 `print' where they work, `printf' if it is available, or else
1 other creative tricks in order to work around the above problems.
1
1 `eval'
1 The `eval' command is useful in limited circumstances, e.g., using
1 commands like `eval table_$key=\$value' and `eval
1 value=table_$key' to simulate a hash table when the key is known
1 to be alphanumeric.
1
1 You should also be wary of common bugs in `eval' implementations.
1 In some shell implementations (e.g., older `ash', OpenBSD 3.8
1 `sh', `pdksh' v5.2.14 99/07/13.2, and `zsh' 4.2.5), the arguments
1 of `eval' are evaluated in a context where `$?' is 0, so they
1 exhibit behavior like this:
1
1 $ false; eval 'echo $?'
1 0
1
1 The correct behavior here is to output a nonzero value, but
1 portable scripts should not rely on this.
1
11 You should not rely on `LINENO' within `eval'. ⇒Special
Shell Variables.
1
1 Note that, even though these bugs are easily avoided, `eval' is
1 tricky to use on arbitrary arguments. It is obviously unwise to
1 use `eval $cmd' if the string value of `cmd' was derived from an
1 untrustworthy source. But even if the string value is valid,
1 `eval $cmd' might not work as intended, since it causes field
1 splitting and file name expansion to occur twice, once for the
1 `eval' and once for the command itself. It is therefore safer to
1 use `eval "$cmd"'. For example, if CMD has the value `cat
1 test?.c', `eval $cmd' might expand to the equivalent of `cat
1 test;.c' if there happens to be a file named `test;.c' in the
1 current directory; and this in turn mistakenly attempts to invoke
1 `cat' on the file `test' and then execute the command `.c'. To
1 avoid this problem, use `eval "$cmd"' rather than `eval $cmd'.
1
1 However, suppose that you want to output the text of the evaluated
1 command just before executing it. Assuming the previous example,
1 `echo "Executing: $cmd"' outputs `Executing: cat test?.c', but
1 this output doesn't show the user that `test;.c' is the actual name
1 of the copied file. Conversely, `eval "echo Executing: $cmd"'
1 works on this example, but it fails with `cmd='cat foo >bar'',
1 since it mistakenly replaces the contents of `bar' by the string
1 `cat foo'. No simple, general, and portable solution to this
1 problem is known.
1
1 `exec'
1 Posix describes several categories of shell built-ins. Special
1 built-ins (such as `exit') must impact the environment of the
1 current shell, and need not be available through `exec'. All
1 other built-ins are regular, and must not propagate variable
1 assignments to the environment of the current shell. However, the
1 group of regular built-ins is further distinguished by commands
1 that do not require a `PATH' search (such as `cd'), in contrast to
1 built-ins that are offered as a more efficient version of
1 something that must still be found in a `PATH' search (such as
1 `echo'). Posix is not clear on whether `exec' must work with the
1 list of 17 utilities that are invoked without a `PATH' search, and
1 many platforms lack an executable for some of those built-ins:
1
1 $ sh -c 'exec cd /tmp'
1 sh: line 0: exec: cd: not found
1
1 All other built-ins that provide utilities specified by Posix must
1 have a counterpart executable that exists on `PATH', although Posix
1 allows `exec' to use the built-in instead of the executable. For
1 example, contrast `bash' 3.2 and `pdksh' 5.2.14:
1
1 $ bash -c 'pwd --version' | head -n1
1 bash: line 0: pwd: --: invalid option
1 pwd: usage: pwd [-LP]
1 $ bash -c 'exec pwd --version' | head -n1
1 pwd (GNU coreutils) 6.10
1 $ pdksh -c 'exec pwd --version' | head -n1
1 pdksh: pwd: --: unknown option
1
1 When it is desired to avoid a regular shell built-in, the
1 workaround is to use some other forwarding command, such as `env'
1 or `nice', that will ensure a path search:
1
1 $ pdksh -c 'exec true --version' | head -n1
1 $ pdksh -c 'nice true --version' | head -n1
1 true (GNU coreutils) 6.10
1 $ pdksh -c 'env true --version' | head -n1
1 true (GNU coreutils) 6.10
1
1 `exit'
1 The default value of `exit' is supposed to be `$?'; unfortunately,
1 some shells, such as the DJGPP port of Bash 2.04, just perform
1 `exit 0'.
1
1 bash-2.04$ foo=`exit 1` || echo fail
1 fail
1 bash-2.04$ foo=`(exit 1)` || echo fail
1 fail
1 bash-2.04$ foo=`(exit 1); exit` || echo fail
1 bash-2.04$
1
1 Using `exit $?' restores the expected behavior.
1
1 Some shell scripts, such as those generated by `autoconf', use a
1 trap to clean up before exiting. If the last shell command exited
1 with nonzero status, the trap also exits with nonzero status so
1 that the invoker can tell that an error occurred.
1
1 Unfortunately, in some shells, such as Solaris `/bin/sh', an exit
1 trap ignores the `exit' command's argument. In these shells, a
1 trap cannot determine whether it was invoked by plain `exit' or by
1 `exit 1'. Instead of calling `exit' directly, use the
1 `AC_MSG_ERROR' macro that has a workaround for this problem.
1
1 `export'
1 The builtin `export' dubs a shell variable "environment variable".
1 Each update of exported variables corresponds to an update of the
1 environment variables. Conversely, each environment variable
1 received by the shell when it is launched should be imported as a
1 shell variable marked as exported.
1
1 Alas, many shells, such as Solaris `/bin/sh', IRIX 6.3, IRIX 5.2,
1 AIX 4.1.5, and Digital Unix 4.0, forget to `export' the
1 environment variables they receive. As a result, two variables
1 coexist: the environment variable and the shell variable. The
1 following code demonstrates this failure:
1
1 #!/bin/sh
1 echo $FOO
1 FOO=bar
1 echo $FOO
1 exec /bin/sh $0
1
1 when run with `FOO=foo' in the environment, these shells print
1 alternately `foo' and `bar', although they should print only `foo'
1 and then a sequence of `bar's.
1
1 Therefore you should `export' again each environment variable that
1 you update; the export can occur before or after the assignment.
1
1 Posix is not clear on whether the `export' of an undefined
1 variable causes the variable to be defined with the value of an
1 empty string, or merely marks any future definition of a variable
1 by that name for export. Various shells behave differently in
1 this regard:
1
1 $ sh -c 'export foo; env | grep foo'
1 $ ash -c 'export foo; env | grep foo'
1 foo=
1
1 Posix requires `export' to honor assignments made as arguments,
1 but older shells do not support this, including `/bin/sh' in
1 Solaris 10. Portable scripts should separate assignments and
1 exports into different statements.
1
1 $ bash -c 'export foo=bar; echo $foo'
1 bar
1 $ /bin/sh -c 'export foo=bar; echo $foo'
1 /bin/sh: foo=bar: is not an identifier
1 $ /bin/sh -c 'export foo; foo=bar; echo $foo'
1 bar
1
1 `false'
1 Don't expect `false' to exit with status 1: in native Solaris
1 `/bin/false' exits with status 255.
1
1 `for'
1 To loop over positional arguments, use:
1
1 for arg
1 do
1 echo "$arg"
1 done
1
1 You may _not_ leave the `do' on the same line as `for', since some
1 shells improperly grok:
1
1 for arg; do
1 echo "$arg"
1 done
1
1 If you want to explicitly refer to the positional arguments, given
1 the `$@' bug (⇒Shell Substitutions), use:
1
1 for arg in ${1+"$@"}; do
1 echo "$arg"
1 done
1
1 But keep in mind that Zsh, even in Bourne shell emulation mode,
11 performs word splitting on `${1+"$@"}'; see ⇒Shell
Substitutions, item `$@', for more.
1
1 In Solaris `/bin/sh', when the list of arguments of a `for' loop
1 starts with _unquoted_ tokens looking like variable assignments,
1 the loop is not executed on those tokens:
1
1 $ /bin/sh -c 'for v in a=b c=d x e=f; do echo $v; done'
1 x
1 e=f
1
1 Thankfully, quoting the assignment-like tokens, or starting the
1 list with other tokens (including unquoted variable expansion that
1 results in an assignment-like result), avoids the problem, so it
1 is easy to work around:
1
1 $ /bin/sh -c 'for v in "a=b"; do echo $v; done'
1 a=b
1 $ /bin/sh -c 'x=a=b; for v in $x c=d; do echo $v; done'
1 a=b
1 c=d
1
1 `if'
1 Using `!' is not portable. Instead of:
1
1 if ! cmp -s file file.new; then
1 mv file.new file
1 fi
1
1 use:
1
1 if cmp -s file file.new; then :; else
1 mv file.new file
1 fi
1
1 Or, especially if the "else" branch is short, you can use `||'.
1 In M4sh, the `AS_IF' macro provides an easy way to write these
1 kinds of conditionals:
1
1 AS_IF([cmp -s file file.new], [], [mv file.new file])
1
1 This is especially useful in other M4 macros, where the "then" and
1 "else" branches might be macro arguments.
1
1 Some very old shells did not reset the exit status from an `if'
1 with no `else':
1
1 $ if (exit 42); then true; fi; echo $?
1 42
1
1 whereas a proper shell should have printed `0'. But this is no
1 longer a portability problem; any shell that supports functions
1 gets it correct. However, it explains why some makefiles have
1 lengthy constructs:
1
1 if test -f "$file"; then
1 install "$file" "$dest"
1 else
1 :
1 fi
1
1 `printf'
1 A format string starting with a `-' can cause problems. Bash
1 interprets it as an option and gives an error. And `--' to mark
1 the end of options is not good in the NetBSD Almquist shell (e.g.,
1 0.4.6) which takes that literally as the format string. Putting
1 the `-' in a `%c' or `%s' is probably easiest:
1
1 printf %s -foo
1
1 Bash 2.03 mishandles an escape sequence that happens to evaluate
1 to `%':
1
1 $ printf '\045'
1 bash: printf: `%': missing format character
1
1 Large outputs may cause trouble. On Solaris 2.5.1 through 10, for
1 example, `/usr/bin/printf' is buggy, so when using `/bin/sh' the
1 command `printf %010000x 123' normally dumps core.
1
1 Since `printf' is not always a shell builtin, there is a potential
1 speed penalty for using `printf '%s\n'' as a replacement for an
1 `echo' that does not interpret `\' or leading `-'. With Solaris
1 `ksh', it is possible to use `print -r --' for this role instead.
1
11 ⇒Limitations of Shell Builtins echo for a discussion of
portable alternatives to both `printf' and `echo'.
1
1 `pwd'
1 With modern shells, plain `pwd' outputs a "logical" directory
1 name, some of whose components may be symbolic links. These
1 directory names are in contrast to "physical" directory names,
1 whose components are all directories.
1
1 Posix 1003.1-2001 requires that `pwd' must support the `-L'
1 ("logical") and `-P' ("physical") options, with `-L' being the
1 default. However, traditional shells do not support these
1 options, and their `pwd' command has the `-P' behavior.
1
1 Portable scripts should assume neither option is supported, and
1 should assume neither behavior is the default. Also, on many hosts
1 `/bin/pwd' is equivalent to `pwd -P', but Posix does not require
1 this behavior and portable scripts should not rely on it.
1
1 Typically it's best to use plain `pwd'. On modern hosts this
1 outputs logical directory names, which have the following
1 advantages:
1
1 * Logical names are what the user specified.
1
1 * Physical names may not be portable from one installation host
1 to another due to network file system gymnastics.
1
1 * On modern hosts `pwd -P' may fail due to lack of permissions
1 to some parent directory, but plain `pwd' cannot fail for this
1 reason.
1
1 Also please see the discussion of the `cd' command.
1
1 `read'
1 No options are portable, not even support `-r' (Solaris `/bin/sh'
1 for example). Tru64/OSF 5.1 `sh' treats `read' as a special
1 built-in, so it may exit if input is redirected from a
1 non-existent or unreadable file.
1
1 `set'
1 With the FreeBSD 6.0 shell, the `set' command (without any
1 options) does not sort its output.
1
1 The `set' builtin faces the usual problem with arguments starting
1 with a dash. Modern shells such as Bash or Zsh understand `--' to
1 specify the end of the options (any argument after `--' is a
1 parameter, even `-x' for instance), but many traditional shells
1 (e.g., Solaris 10 `/bin/sh') simply stop option processing as soon
1 as a non-option argument is found. Therefore, use `dummy' or
1 simply `x' to end the option processing, and use `shift' to pop it
1 out:
1
1 set x $my_list; shift
1
1 Avoid `set -', e.g., `set - $my_list'. Posix no longer requires
1 support for this command, and in traditional shells `set -
1 $my_list' resets the `-v' and `-x' options, which makes scripts
1 harder to debug.
1
1 Some nonstandard shells do not recognize more than one option
1 (e.g., `set -e -x' assigns `-x' to the command line). It is
1 better to combine them:
1
1 set -ex
1
1 The option `-e' has historically been underspecified, with enough
1 ambiguities to cause numerous differences across various shell
1 implementations; see for example this overview
1 (http://www.in-ulm.de/~mascheck/various/set-e/), or this link
1 (http://www.austingroupbugs.net/view.php?id=52), documenting a
1 change to Posix 2008 to match `ksh88' behavior. Note that mixing
1 `set -e' and shell functions is asking for surprises:
1
1 set -e
1 doit()
1 {
1 rm file
1 echo one
1 }
1 doit || echo two
1
1 According to the recommendation, `one' should always be output
1 regardless of whether the `rm' failed, because it occurs within
1 the body of the shell function `doit' invoked on the left side of
1 `||', where the effects of `set -e' are not enforced. Likewise,
1 `two' should never be printed, since the failure of `rm' does not
1 abort the function, such that the status of `doit' is 0.
1
1 The BSD shell has had several problems with the `-e' option.
1 Older versions of the BSD shell (circa 1990) mishandled `&&',
1 `||', `if', and `case' when `-e' was in effect, causing the shell
1 to exit unexpectedly in some cases. This was particularly a
1 problem with makefiles, and led to circumlocutions like `sh -c
1 'test -f file || touch file'', where the seemingly-unnecessary `sh
11 -c '...'' wrapper works around the bug (⇒Failure in Make
Rules).
1
1 Even relatively-recent versions of the BSD shell (e.g., OpenBSD
1 3.4) wrongly exit with `-e' if the last command within a compound
1 statement fails and is guarded by an `&&' only. For example:
1
1 #! /bin/sh
1 set -e
1 foo=''
1 test -n "$foo" && exit 1
1 echo one
1 if :; then
1 test -n "$foo" && exit 1
1 echo two
1 test -n "$foo" && exit 1
1 fi
1 echo three
1
1 does not print `three'. One workaround is to change the last
1 instance of `test -n "$foo" && exit 1' to be `if test -n "$foo";
1 then exit 1; fi' instead. Another possibility is to warn BSD
1 users not to use `sh -e'.
1
1 When `set -e' is in effect, a failed command substitution in
1 Solaris `/bin/sh' cannot be ignored, even with `||'.
1
1 $ /bin/sh -c 'set -e; foo=`false` || echo foo; echo bar'
1 $ bash -c 'set -e; foo=`false` || echo foo; echo bar'
1 foo
1 bar
1
1 Moreover, a command substitution, successful or not, causes this
1 shell to exit from a failing outer command even in presence of an
1 `&&' list:
1
1 $ bash -c 'set -e; false `true` && echo notreached; echo ok'
1 ok
1 $ sh -c 'set -e; false `true` && echo notreached; echo ok'
1 $
1
1 Portable scripts should not use `set -e' if `trap' is used to
1 install an exit handler. This is because Tru64/OSF 5.1 `sh'
1 sometimes enters the trap handler with the exit status of the
1 command prior to the one that triggered the errexit handler:
1
1 $ sh -ec 'trap '\''echo $?'\'' 0; false'
1 0
1 $ sh -c 'set -e; trap '\''echo $?'\'' 0; false'
1 1
1
1 Thus, when writing a script in M4sh, rather than trying to rely on
1 `set -e', it is better to append `|| AS_EXIT' to any statement
1 where it is desirable to abort on failure.
1
1 Job control is not provided by all shells, so the use of `set -m'
1 or `set -b' must be done with care. When using `zsh' in native
1 mode, asynchronous notification (`set -b') is enabled by default,
1 and using `emulate sh' to switch to Posix mode does not clear this
1 setting (although asynchronous notification has no impact unless
1 job monitoring is also enabled). Also, `zsh' 4.3.10 and earlier
1 have a bug where job control can be manipulated in interactive
1 shells, but not in subshells or scripts. Furthermore, some
1 shells, like `pdksh', fail to treat subshells as interactive, even
1 though the parent shell was.
1
1 $ echo $ZSH_VERSION
1 4.3.10
1 $ set -m; echo $?
1 0
1 $ zsh -c 'set -m; echo $?'
1 set: can't change option: -m
1 $ (set -m); echo $?
1 set: can't change option: -m
1 1
1 $ pdksh -ci 'echo $-; (echo $-)'
1 cim
1 c
1
1 Use of `set -n' (typically via `sh -n script') to validate a
1 script is not foolproof. Modern `ksh93' tries to be helpful by
1 informing you about better syntax, but switching the script to use
1 the suggested syntax in order to silence the warnings would render
1 the script no longer portable to older shells:
1
1 $ ksh -nc '``'
1 ksh: warning: line 1: `...` obsolete, use $(...)
1 0
1
1 Furthermore, on ancient hosts, such as SunOS 4, `sh -n' could go
1 into an infinite loop; even with that bug fixed, Solaris 8
1 `/bin/sh' takes extremely long to parse large scripts. Autoconf
1 itself uses `sh -n' within its testsuite to check that correct
1 scripts were generated, but only after first probing for other
1 shell features (such as `test -n "${BASH_VERSION+set}"') that
1 indicate a reasonably fast and working implementation.
1
1 `shift'
1 Not only is `shift'ing a bad idea when there is nothing left to
1 shift, but in addition it is not portable: the shell of MIPS
1 RISC/OS 4.52 refuses to do it.
1
1 Don't use `shift 2' etc.; while it in the SVR1 shell (1983), it is
1 also absent in many pre-Posix shells.
1
1 `source'
1 This command is not portable, as Posix does not require it; use
1 `.' instead.
1
1 `test'
1 The `test' program is the way to perform many file and string
1 tests. It is often invoked by the alternate name `[', but using
1 that name in Autoconf code is asking for trouble since it is an M4
1 quote character.
1
1 The `-a', `-o', `(', and `)' operands are not present in all
1 implementations, and have been marked obsolete by Posix 2008.
1 This is because there are inherent ambiguities in using them. For
1 example, `test "$1" -a "$2"' looks like a binary operator to check
1 whether two strings are both non-empty, but if `$1' is the literal
1 `!', then some implementations of `test' treat it as a negation of
1 the unary operator `-a'.
1
1 Thus, portable uses of `test' should never have more than four
1 arguments, and scripts should use shell constructs like `&&' and
1 `||' instead. If you combine `&&' and `||' in the same statement,
1 keep in mind that they have equal precedence, so it is often
1 better to parenthesize even when this is redundant. For example:
1
1 # Not portable:
1 test "X$a" = "X$b" -a \
1 '(' "X$c" != "X$d" -o "X$e" = "X$f" ')'
1
1 # Portable:
1 test "X$a" = "X$b" &&
1 { test "X$c" != "X$d" || test "X$e" = "X$f"; }
1
1 `test' does not process options like most other commands do; for
1 example, it does not recognize the `--' argument as marking the
1 end of options.
1
1 It is safe to use `!' as a `test' operator. For example, `if test
1 ! -d foo; ...' is portable even though `if ! test -d foo; ...' is
1 not.
1
1 `test' (files)
1 To enable `configure' scripts to support cross-compilation, they
1 shouldn't do anything that tests features of the build system
1 instead of the host system. But occasionally you may find it
1 necessary to check whether some arbitrary file exists. To do so,
1 use `test -f', `test -r', or `test -x'. Do not use `test -e',
1 because Solaris 10 `/bin/sh' lacks it. To test for symbolic links
1 on systems that have them, use `test -h' rather than `test -L';
1 either form conforms to Posix 1003.1-2001, but older shells like
1 Solaris 8 `/bin/sh' support only `-h'.
1
1 For historical reasons, Posix reluctantly allows implementations of
1 `test -x' that will succeed for the root user, even if no execute
1 permissions are present. Furthermore, shells do not all agree on
1 whether Access Control Lists should affect `test -r', `test -w',
1 and `test -x'; some shells base test results strictly on the
1 current user id compared to file owner and mode, as if by
1 `stat(2)'; while other shells base test results on whether the
1 current user has the given right, even if that right is only
1 granted by an ACL, as if by `faccessat(2)'. Furthermore, there is
1 a classic time of check to time of use race between any use of
1 `test' followed by operating on the just-checked file. Therefore,
1 it is a good idea to write scripts that actually attempt an
1 operation, and are prepared for the resulting failure if
1 permission is denied, rather than trying to avoid an operation
1 based solely on whether `test' guessed that it might not be
1 permitted.
1
1 `test' (strings)
1 Posix says that `test "STRING"' succeeds if STRING is not null,
1 but this usage is not portable to traditional platforms like
1 Solaris 10 `/bin/sh', which mishandle strings like `!' and `-n'.
1
1 Posix also says that `test ! "STRING"', `test -n "STRING"' and
1 `test -z "STRING"' work with any string, but many shells (such as
1 Solaris, AIX 3.2, UNICOS 10.0.0.6, Digital Unix 4, etc.) get
1 confused if STRING looks like an operator:
1
1 $ test -n =
1 test: argument expected
1 $ test ! -n
1 test: argument expected
1 $ test -z ")"; echo $?
1 0
1
1 Similarly, Posix says that both `test "STRING1" = "STRING2"' and
1 `test "STRING1" != "STRING2"' work for any pairs of strings, but
1 in practice this is not true for troublesome strings that look
1 like operators or parentheses, or that begin with `-'.
1
1 It is best to protect such strings with a leading `X', e.g., `test
1 "XSTRING" != X' rather than `test -n "STRING"' or `test !
1 "STRING"'.
1
1 It is common to find variations of the following idiom:
1
1 test -n "`echo $ac_feature | sed 's/[-a-zA-Z0-9_]//g'`" &&
1 ACTION
1
1 to take an action when a token matches a given pattern. Such
1 constructs should be avoided by using:
1
1 case $ac_feature in
1 *[!-a-zA-Z0-9_]*) ACTION;;
1 esac
1
1 If the pattern is a complicated regular expression that cannot be
1 expressed as a shell pattern, use something like this instead:
1
1 expr "X$ac_feature" : 'X.*[^-a-zA-Z0-9_]' >/dev/null &&
1 ACTION
1
1 `expr "XFOO" : "XBAR"' is more robust than `echo "XFOO" | grep
1 "^XBAR"', because it avoids problems when `FOO' contains
1 backslashes.
1
1 `trap'
1 It is safe to trap at least the signals 1, 2, 13, and 15. You can
1 also trap 0, i.e., have the `trap' run when the script ends
1 (either via an explicit `exit', or the end of the script). The
1 trap for 0 should be installed outside of a shell function, or AIX
1 5.3 `/bin/sh' will invoke the trap at the end of this function.
1
1 Posix says that `trap - 1 2 13 15' resets the traps for the
1 specified signals to their default values, but many common shells
1 (e.g., Solaris `/bin/sh') misinterpret this and attempt to execute
1 a "command" named `-' when the specified conditions arise. Posix
1 2008 also added a requirement to support `trap 1 2 13 15' to reset
1 traps, as this is supported by a larger set of shells, but there
1 are still shells like `dash' that mistakenly try to execute `1'
1 instead of resetting the traps. Therefore, there is no portable
1 workaround, except for `trap - 0', for which `trap '' 0' is a
1 portable substitute.
1
1 Although Posix is not absolutely clear on this point, it is widely
1 admitted that when entering the trap `$?' should be set to the exit
1 status of the last command run before the trap. The ambiguity can
1 be summarized as: "when the trap is launched by an `exit', what is
1 the _last_ command run: that before `exit', or `exit' itself?"
1
1 Bash considers `exit' to be the last command, while Zsh and
1 Solaris `/bin/sh' consider that when the trap is run it is _still_
1 in the `exit', hence it is the previous exit status that the trap
1 receives:
1
1 $ cat trap.sh
1 trap 'echo $?' 0
1 (exit 42); exit 0
1 $ zsh trap.sh
1 42
1 $ bash trap.sh
1 0
1
1 The portable solution is then simple: when you want to `exit 42',
1 run `(exit 42); exit 42', the first `exit' being used to set the
1 exit status to 42 for Zsh, and the second to trigger the trap and
1 pass 42 as exit status for Bash. In M4sh, this is covered by using
1 `AS_EXIT'.
1
1 The shell in FreeBSD 4.0 has the following bug: `$?' is reset to 0
1 by empty lines if the code is inside `trap'.
1
1 $ trap 'false
1
1 echo $?' 0
1 $ exit
1 0
1
1 Fortunately, this bug only affects `trap'.
1
1 Several shells fail to execute an exit trap that is defined inside
1 a subshell, when the last command of that subshell is not a
1 builtin. A workaround is to use `exit $?' as the shell builtin.
1
1 $ bash -c '(trap "echo hi" 0; /bin/true)'
1 hi
1 $ /bin/sh -c '(trap "echo hi" 0; /bin/true)'
1 $ /bin/sh -c '(trap "echo hi" 0; /bin/true; exit $?)'
1 hi
1
1 Likewise, older implementations of `bash' failed to preserve `$?'
1 across an exit trap consisting of a single cleanup command.
1
1 $ bash -c 'trap "/bin/true" 0; exit 2'; echo $?
1 2
1 $ bash-2.05b -c 'trap "/bin/true" 0; exit 2'; echo $?
1 0
1 $ bash-2.05b -c 'trap ":; /bin/true" 0; exit 2'; echo $?
1 2
1
1 `true'
1 Don't worry: as far as we know `true' is portable. Nevertheless,
1 it's not always a builtin (e.g., Bash 1.x), and the portable shell
1 community tends to prefer using `:'. This has a funny side
1 effect: when asked whether `false' is more portable than `true'
1 Alexandre Oliva answered:
1
1 In a sense, yes, because if it doesn't exist, the shell will
1 produce an exit status of failure, which is correct for
1 `false', but not for `true'.
1
1 Remember that even though `:' ignores its arguments, it still takes
1 time to compute those arguments. It is a good idea to use double
1 quotes around any arguments to `:' to avoid time spent in field
1 splitting and file name expansion.
1
1 `unset'
1 In some nonconforming shells (e.g., Solaris 10 `/bin/ksh' and
1 `/usr/xpg4/bin/sh', NetBSD 5.99.43 sh, or Bash 2.05a), `unset FOO'
1 fails when `FOO' is not set. This can interfere with `set -e'
1 operation. You can use
1
1 FOO=; unset FOO
1
1 if you are not sure that `FOO' is set.
1
1 A few ancient shells lack `unset' entirely. For some variables
1 such as `PS1', you can use a neutralizing value instead:
1
1 PS1='$ '
1
1 Usually, shells that do not support `unset' need less effort to
1 make the environment sane, so for example is not a problem if you
1 cannot unset `CDPATH' on those shells. However, Bash 2.01
1 mishandles `unset MAIL' and `unset MAILPATH' in some cases and
1 dumps core. So, you should do something like
1
1 ( (unset MAIL) || exit 1) >/dev/null 2>&1 && unset MAIL || :
1
1 ⇒Special Shell Variables, for some neutralizing values.
1 Also, see ⇒Limitations of Builtins export, for the case of
1 environment variables.
1
1 `wait'
1 The exit status of `wait' is not always reliable.
1