File: autoconf.info, Node: Shell Substitutions, Next: Assignments, Prev: Shell Pattern Matching, Up: Portable Shell 11.8 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="`echo "$dots" | sed 's|/$||'`" ;; *) top_srcdir="$dots$given_srcdir" ;; esac is more readable when written as: case $given_srcdir in .) top_srcdir=`echo "$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: ‘$@’ One of the most famous shell-portability issues is related to ‘"$@"’. When there are no positional arguments, Posix says that ‘"$@"’ is supposed to be equivalent to nothing, but the original Unix version 7 Bourne shell treated it as equivalent to ‘""’ instead, and this behavior survives in later implementations like Digital Unix 5.0. The traditional way to work around this portability problem is to use ‘${1+"$@"}’. Unfortunately this method does not work with Zsh (3.x and 4.x), which is used on Mac OS X. When emulating the Bourne shell, Zsh performs word splitting on ‘${1+"$@"}’: zsh $ emulate sh zsh $ for i in "$@"; do echo $i; done Hello World ! zsh $ for i in ${1+"$@"}; do echo $i; done Hello World ! Zsh handles plain ‘"$@"’ properly, but we can't use plain ‘"$@"’ because of the portability problems mentioned above. One workaround relies on Zsh's "global aliases" to convert ‘${1+"$@"}’ into ‘"$@"’ by itself: test ${ZSH_VERSION+y} && alias -g '${1+"$@"}'='"$@"' Zsh only recognizes this alias when a shell word matches it exactly; ‘"foo"${1+"$@"}’ remains subject to word splitting. Since this case always yields at least one shell word, use plain ‘"$@"’. A more conservative workaround is to avoid ‘"$@"’ if it is possible that there may be no positional arguments. For example, instead of: cat conftest.c "$@" you can use this instead: case $# in 0) cat conftest.c;; *) cat conftest.c "$@";; esac 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. ‘${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}’ Old BSD shells, including the Ultrix ‘sh’, don't accept the colon for any shell substitution, and complain and die. Similarly for ‘${VAR:=VALUE}’, ‘${VAR:?VALUE}’, etc. However, all shells that support functions allow the use of colon in shell substitution, and since m4sh requires functions, you can portably use null variable substitution patterns in configure scripts. ‘${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 nor does IRIX 6.5's Bourne shell: $ uname -a IRIX firebird-image 6.5 07151432 IP22 $ echo $(echo blah) $(echo blah) 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 ‘|’.