On Saturday, July 27, 2013 10:06:34 PM Chet Ramey wrote: > I'm leaving things as they are for the time being.
I'd only change it if somebody thinks portability is more important than backwards-compatibility. Currently this works most places: typeset -ia arr arr+=(1+1 2+2) which avoids unsetting the attribute in those shells that do so without +=. BTW, evaluation order can get quite complicated especially when integer attributes are involved. This table reveals some interesting properties such as Bash's short-circuiting and sometimes double-evaluation of parameter expansions. I wrote the test runner in ksh: ------------------ output Each testcase prints evaluation order for indexed array assignment contexts. Each context is tested for expansions (represented by digits) and arithmetic (letters), ordered from left to right within the expression. The output corresponds to the way evaluation is re-ordered for each shell: a[ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} No attributes a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia b a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} typeset -ia a b (( a[ $1 a ] = b[ $2 b ] ${c[ $3 c ]} )) No attributes (( a[ $1 a ] = ${b[ $2 b ]:=c[ $3 c ]} )) typeset -ia b a+=( [ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} [ $4 d ]=$(( $5 e )) ) typeset -a a a+=( [ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} [ $4 d ]=${5}e ) typeset -ia a bash: 4.2.45(1)-release 2 b 3 c 2 b 1 a 2 b 3 2 b 1 a c 2 b 3 2 b c 1 a 2 b 3 2 b c 1 a c 1 2 3 c b a 1 2 b 3 2 b c c a 1 2 b 3 c 2 b 4 5 e a d 1 2 b 3 2 b 4 5 a c d e bash: 4.3.0(1)-beta 2 b 3 c 2 b 1 a 2 b 3 2 b 1 a c 2 b 3 2 b c 1 a 2 b 3 2 b c 1 a c 1 2 3 c b a 1 2 b 3 2 b c c a 1 2 b 3 c 2 b 4 5 e a d 1 2 b 3 2 b 4 5 a c d e ksh93: Version AIJM 93v- 2013-07-18 1 2 b a 1 2 b a 1 2 b a 1 2 b a 1 2 3 c b a 1 2 b a 1 2 b a 4 5 e d 1 2 b a 4 5 d e mksh: @(#)MIRBSD KSH R47 2013/07/25 2 b 3 c 1 a 2 b 3 1 a c 2 b 3 c 1 a 2 b 3 c 1 a 1 2 3 c a b 1 2 b 3 c a 1 2 b 3 c 4 5 e a d 1 2 b 3 4 5 a c d e zsh: 5.0.2 2 b 3 c 2 b 1 a 2 b 3 2 b 1 a c 2 b 1 a 2 b 1 a 1 2 3 c b a 1 2 b a 1 2 b 3 c 2 b 4 5 e 1 2 b 3 2 b 4 5 ------------------ #!/usr/bin/env ksh # Testcase runs in ksh93t or greater. Tests should run in any shell provided # you can supply all the necessary workarounds, and they correctly interpret # ksh93 printf %q output (requires $'...'). At least one level of recursive # arithmetic variable evaluation must also be supported. # Dan Douglas <orm...@gmail.com> namespace main { # e.g. add "set -x" to hacks typeset -A shells=( [mksh]=( typeset -a hacks=('unset -v _') vvar='print -r -- "mksh: $KSH_VERSION"' ) [ksh]=( typeset -a hacks=( 'typeset -a kshKludge alpha=({a..z})' 'function .sh.math.kshKludge x { printf "%s " ${alpha[x]}; }' 'function kshKludge.get { .sh.value=${.sh.name}\(${.sh.subscript}\); }' ) vvar='print -r -- "ksh93: ${.sh.version}"' ) [zsh]=( typeset -a hacks=('emulate ksh') vvar='print -r -- "zsh: $ZSH_VERSION"' ) [bash]=( typeset -a hacks=('shopt -s extglob lastpipe') vvar='printf %s\\n "bash: $BASH_VERSION"' ) [bash43]=( typeset -a hacks=('shopt -s extglob lastpipe') vvar='printf %s\\n "bash: $BASH_VERSION"' path=~/doc/programs/bash43 ) ) typeset -a tests=( # Simple assignment: ( docstring='a[ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}}' setup= testcase='a[$(printf "1 " >&3)x[0]]=${b[$(printf "2 " >&3)${kshKludge[1]:-x[1]},0]:=${c[$(printf "3 " >&3)x[2]]}}' ) # Integer array simple assignment: ( docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}' setup='typeset -ia a' testcase='a[$(printf "1 " >&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 " >&3)x[2]]}' ) # simple assignment w/ integer PE assignment: ( docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}' setup='typeset -ia b' testcase='a[$(printf "1 " >&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 " >&3)x[2]]}' ) # Both Integer array simple assignment + integer PE assignment: ( docstring='a[ $1 a ]=${b[ $2 b ]:=c[ $3 c ]}' setup='typeset -ia a b' testcase='a[$(printf "1 " >&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 " >&3)x[2]]}' ) # Array assignment within arithmetic context: ( docstring='(( a[ $1 a ] = b[ $2 b ] ${c[ $3 c ]} ))' setup= testcase='((a[$(printf "1 " >&3)x[0]]=b[$(printf "2 " >&3)x[1],1]${c[${kshKludge[2]:-x[2]}$(printf "3 " >&3),1]}))' ) # Array assignment within arithmetic context + PE assignment: ( docstring='(( a[ $1 a ] = ${b[ $2 b ]:=c[ $3 c ]} ))' setup='typeset -ia b' testcase='((a[$(printf "1 " >&3)x[0]]=${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 " >&3)x[2],1]}))' ) # Compound assignment ( docstring='a+=( [ $1 a ]=${b[ $2 b ]:=${c[ $3 c ]}} [ $4 d ]=$(( $5 e )) )' setup='typeset -a a' testcase='a+=([x[0]$(printf "1 " >&3),0]="${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=${c[$(printf "3 " >&3)x[2],1]}}" [x[3]$(printf "4 " >&3)]="$((x[4]$(printf "5 " >&3)))")' ) # Compound integer array assignment ( docstring='a+=( [ $1 a ]=${b[ $2 b ]:=c[ $3 c ]} [ $4 d ]=${5}e )' setup='typeset -ia a' testcase='a+=([x[0]$(printf "1 " >&3),0]="${b[${kshKludge[1]:-x[1]}$(printf "2 " >&3),0]:=c[$(printf "3 " >&3)x[2],1]}" [x[3]$(printf "4 " >&3)]="x[4]$(printf "5 " >&3)")' ) ) # Allow sort-of Bash-like -v logic with printf. Nice ksh command # substitution performance at the cost of stripping trailing newlines function printfv { if [[ $1 == -v && ${2:+_} ]]; then nameref x=$2 shift 2 x=$(command printf "$@") else command printf "$@" fi } # Combine commands to eval sequentially. Builds a "qball". # ''eval $qball'' unravels it. # Strings of nested escaping grow faster as nesting gets deeper! # qball [ -v var ] var1 var2 var3 ... function qball { if [[ $1 == -v && ${2:+_} ]]; then nameref ret=$2 typeset assign=ret shift 2 else typeset assign fi typeset x ref=x while ! ${1:+false}; do nameref var=$1 ${2:+:} typeset ref=$assign ${var:+printfv ${ref:+-v "$ref"} 'eval %q' "${x:+${x};}${var}"} shift done } # Build scripts from the given template vars and run the testcases. # Normally I dislike "shell templates". This seems an acceptable usage. function runTests { nameref t=$1 s=$2 typeset ts sh # Bundle setup + testcase pairs into an eval-able tuple. This # guarantees sequential commands are safely concatenated into one # variable that may be evaluated within the same subshell. # Print docstrings at the same time. typeset -a allTests for ts in "${!t[@]}"; do allTests+=("$(qball ${t[ts].setup:+"t[$ts].setup"} "t[${ts}].testcase")") printf '%-65s %s\n' "${t[ts].docstring}" "${t[ts].setup:-No attributes}" done echo # This isn't as confusing as it looks. The outer heredoc is the # testcase part, which only needs to expand once. The inner heredoc is # shell-specific code expanded for each iteration. for sh in "${!s[@]}"; do { printf '%s\n' "${ { _=$("${s[$sh].path:-$sh}" -s </dev/fd/0 2>&1 >&3); } 3>&1;}" ${_:+"${sh} errors:" "$_"} echo } <<-EOF set -f # Setup shell-specific hacks typeset z for z in $(printf '%q ' "${s[$sh].hacks[@]}"); do eval "\$z" done # Print the shell version string ${s[$sh].vvar} # Inject the testcase code (probably faster than sourcing.) $(</dev/fd/4) EOF done 3<&0 <<-EOF 4<&0 <&3- function main { # A variable to access through arithmetic recursion. Sadly, not all # shells support nice sequence expansions. typeset -a $(print -rn -- {a..z}) x+=($(printf '%q ' 'c[$(printf "%s " '{a..z}' >&3)1]')) # Run the actual tests. typeset testCode for testCode; do (eval "\$testCode") echo done 3>&1 } # Ensure single-letter globals are free unset -v $(print -rn -- {a..z}) main $(printf '%q ' "${allTests[@]}") EOF } cat <<-"EOF" Each testcase prints evaluation order for indexed array assignment contexts. Each context is tested for expansions (represented by digits) and arithmetic (letters), ordered from left to right within the expression. The output corresponds to the way evaluation is re-ordered for each shell: EOF echo set -f # optional. We don't glob anywhere. # typeset -ft runTests runTests tests shells } # vim: set fenc=utf-8 ff=unix ft=sh : -- Dan Douglas