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