manpagez: man pages & more
info autoconf
Home | html | info | man

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 <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.

© manpagez.com 2000-2024
Individual documents may contain additional copyright information.