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

File: autoconf.info,  Node: Shell Substitutions,  Next: Assignments,  Prev: Shell Pattern Matching,  Up: Portable Shell

11.9 Shell Substitutions
========================

Contrary to a persistent urban legend, the Bourne shell does not
systematically split variables and back-quoted expressions, in
particular on the right-hand side of assignments and in the argument of
‘case’.  For instance, the following code:

     case "$given_srcdir" in
     .)  top_srcdir="`printf '%s\n' "$dots" | sed 's|/$||'`" ;;
     *)  top_srcdir="$dots$given_srcdir" ;;
     esac

is more readable when written as:

     case $given_srcdir in
     .)  top_srcdir=`printf '%s\n' "$dots" | sed 's|/$||'` ;;
     *)  top_srcdir=$dots$given_srcdir ;;
     esac

and in fact it is even _more_ portable: in the first case of the first
attempt, the computation of ‘top_srcdir’ is not portable, since not all
shells properly understand ‘"`..."..."...`"’, for example Solaris 10
‘ksh’:

     $ foo="`echo " bar" | sed 's, ,,'`"
     ksh: : cannot execute
     ksh: bar | sed 's, ,,': cannot execute

POSIX does not specify behavior for this sequence.  On the other hand,
behavior for ‘"`...\"...\"...`"’ is specified by POSIX, but in practice,
not all shells understand it the same way: pdksh 5.2.14 prints spurious
quotes when in POSIX mode:

     $ echo "`echo \"hello\"`"
     hello
     $ set -o posix
     $ echo "`echo \"hello\"`"
     "hello"

There is just no portable way to use double-quoted strings inside
double-quoted back-quoted expressions (pfew!).

   Bash 4.1 has a bug where quoted empty strings adjacent to unquoted
parameter expansions are elided during word splitting.  Meanwhile, zsh
does not perform word splitting except when in Bourne compatibility
mode.  In the example below, the correct behavior is to have five
arguments to the function, and exactly two spaces on either side of the
middle ‘-’, since word splitting collapses multiple spaces in ‘$f’ but
leaves empty arguments intact.

     $ bash -c 'n() { echo "$#$@"; }; f="  -  "; n - ""$f"" -'
     3- - -
     $ ksh -c 'n() { echo "$#$@"; }; f="  -  "; n - ""$f"" -'
     5-  -  -
     $ zsh -c 'n() { echo "$#$@"; }; f="  -  "; n - ""$f"" -'
     3-   -   -
     $ zsh -c 'emulate sh;
     > n() { echo "$#$@"; }; f="  -  "; n - ""$f"" -'
     5-  -  -

You can work around this by doing manual word splitting, such as using
‘"$str" $list’ rather than ‘"$str"$list’.

   There are also portability pitfalls with particular expansions:

‘$@’
     Autoconf macros often use the ‘set’ command to update ‘$@’, so if
     you are writing shell code intended for ‘configure’ you should not
     assume that the value of ‘$@’ persists for any length of time.

     You may see usages like ‘${1+"$@"}’ in older shell scripts designed
     to work around a portability problem in ancient shells.
     Unfortunately this runs afoul of bugs in more-recent shells, and
     nowadays it is better to use plain ‘"$@"’ instead.

     The portability problem with ancient shells was significant.  When
     there are no positional arguments ‘"$@"’ should be discarded, but
     the original Unix version 7 Bourne shell mistakenly treated it as
     equivalent to ‘""’ instead, and many ancient shells followed its
     lead.

     For many years shell scripts worked around this portability problem
     by using ‘${1+"$@"}’ instead of ‘"$@"’, and you may see this usage
     in older scripts.  Unfortunately, ‘${1+"$@"}’ does not work with
     ‘ksh93’ M 93t+ (2009) as shipped in AIX 7.2 (2015), as this shell
     drops a trailing empty argument:

          $ set a b c ""
          $ set ${1+"$@"}
          $ echo $#
          3

     Also, ‘${1+"$@"}’ does not work with Zsh 4.2.6 (2005) and earlier,
     as shipped in Mac OS X releases before 10.5, as this old Zsh
     incorrectly word splits the result:

          zsh $ emulate sh
          zsh $ for i in "$@"; do echo $i; done
          Hello World
          !
          zsh $ for i in ${1+"$@"}; do echo $i; done
          Hello
          World
          !

     To work around these problems Autoconf does two things.  First, in
     the shell code that it generates Autoconf avoids ‘"$@"’ if it is
     possible that there may be no positional arguments.  You can use
     this workaround in your own code, too, if you want it to be
     portable to ancient shells.  For example, instead of:

          cat conftest.c "$@"

     you can use this:

          case $# in
            0) cat conftest.c;;
            *) cat conftest.c "$@";;
          esac

     Second, Autoconf-generated ‘configure’ scripts work around most of
     the old Zsh problem by using Zsh’s “global aliases” to convert
     ‘${1+"$@"}’ into ‘"$@"’ by itself:

          test ${ZSH_VERSION+y} && alias -g '${1+"$@"}'='"$@"'

     This workaround is for the benefit of any instances of ‘${1+"$@"}’
     in user-written code appearing in ‘configure’ scripts.  However, it
     is not a complete solution, as Zsh recognizes the alias only when a
     shell word matches it exactly, which means older Zsh still
     mishandles more-complicated cases like ‘"foo"${1+"$@"}’.

‘${10}’
     The 10th, 11th, ... positional parameters can be accessed only
     after a ‘shift’.  The 7th Edition shell reported an error if given
     ‘${10}’, and Solaris 10 ‘/bin/sh’ still acts that way:

          $ set 1 2 3 4 5 6 7 8 9 10
          $ echo ${10}
          bad substitution

     Conversely, not all shells obey the POSIX rule that when braces are
     omitted, multiple digits beyond a ‘$’ imply the single-digit
     positional parameter expansion concatenated with the remaining
     literal digits.  To work around the issue, you must use braces.

          $ bash -c 'set a b c d e f g h i j; echo $10 ${1}0'
          a0 a0
          $ dash -c 'set a b c d e f g h i j; echo $10 ${1}0'
          j a0

‘${VAR-VALUE}’
‘${VAR:-VALUE}’
‘${VAR=VALUE}’
‘${VAR:=VALUE}’
‘${VAR?VALUE}’
‘${VAR:?VALUE}’
‘${VAR+VALUE}’
‘${VAR:+VALUE}’
     When using ‘${VAR-VALUE}’ or similar notations that modify a
     parameter expansion, POSIX requires that VALUE must be a single
     shell word, which can contain quoted strings but cannot contain
     unquoted spaces.  If this requirement is not met Solaris 10
     ‘/bin/sh’ sometimes complains, and anyway the behavior is not
     portable.

          $ /bin/sh -c 'echo ${a-b c}'
          /bin/sh: bad substitution
          $ /bin/sh -c 'echo ${a-'\''b c'\''}'
          b c
          $ /bin/sh -c 'echo "${a-b c}"'
          b c
          $ /bin/sh -c 'cat < broken
          $ echo "`printf 'foo\r\n'`"" bar" | cmp - broken
          - broken differ: char 4, line 1

     Upon interrupt or SIGTERM, some shells may abort a command
     substitution, replace it with a null string, and wrongly evaluate
     the enclosing command before entering the trap or ending the
     script.  This can lead to spurious errors:

          $ sh -c 'if test `sleep 5; echo hi` = hi; then echo yes; fi'
          $ ^C
          sh: test: hi: unexpected operator/operand

     You can avoid this by assigning the command substitution to a
     temporary variable:

          $ sh -c 'res=`sleep 5; echo hi`
                   if test "x$res" = xhi; then echo yes; fi'
          $ ^C

‘$(COMMANDS)’
     This construct is meant to replace ‘`COMMANDS`’, and it has most of
     the problems listed under ‘`COMMANDS`’.

     This construct can be nested while this is impossible to do
     portably with back quotes.  Although it is almost universally
     supported, unfortunately Solaris 10 and earlier releases lack it:

          $ showrev -c /bin/sh | grep version
          Command version: SunOS 5.10 Generic 142251-02 Sep 2010
          $ echo $(echo blah)
          syntax error: `(' unexpected

     If you do use ‘$(COMMANDS)’, make sure that the commands do not
     start with a parenthesis, as that would cause confusion with a
     different notation ‘$((EXPRESSION))’ that in modern shells is an
     arithmetic expression not a command.  To avoid the confusion,
     insert a space between the two opening parentheses.

     Avoid COMMANDS that contain unbalanced parentheses in
     here-documents, comments, or case statement patterns, as many
     shells mishandle them.  For example, Bash 3.1, ‘ksh88’, ‘pdksh’
     5.2.14, and Zsh 4.2.6 all mishandle the following valid command:

          echo $(case x in x) echo hello;; esac)

‘$((EXPRESSION))’
     Arithmetic expansion is not portable as some shells (most notably
     Solaris 10 ‘/bin/sh’) don’t support it.

     Among shells that do support ‘$(( ))’, not all of them obey the
     POSIX rule that octal and hexadecimal constants must be recognized:

          $ bash -c 'echo $(( 010 + 0x10 ))'
          24
          $ zsh -c 'echo $(( 010 + 0x10 ))'
          26
          $ zsh -c 'emulate sh; echo $(( 010 + 0x10 ))'
          24
          $ pdksh -c 'echo $(( 010 + 0x10 ))'
          pdksh:  010 + 0x10 : bad number `0x10'
          $ pdksh -c 'echo $(( 010 ))'
          10

     When it is available, using arithmetic expansion provides a
     noticeable speedup in script execution; but testing for support
     requires ‘eval’ to avoid syntax errors.  The following construct is
     used by ‘AS_VAR_ARITH’ to provide arithmetic computation when all
     arguments are decimal integers without leading zeros, and all
     operators are properly quoted and appear as distinct arguments:

          if ( eval 'test $(( 1 + 1 )) = 2' ) 2>/dev/null; then
            eval 'func_arith ()
            {
              func_arith_result=$(( $* ))
            }'
          else
            func_arith ()
            {
              func_arith_result=`expr "$@"`
            }
          fi
          func_arith 1 + 1
          foo=$func_arith_result

‘^’
     Always quote ‘^’, otherwise traditional shells such as ‘/bin/sh’ on
     Solaris 10 treat this like ‘|’.

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