bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok


From: Dan Douglas
Subject: Re: Minor bug declaring arrays of integers: dcl -ai=>broken, dcl -ia=>ok
Date: Sun, 28 Jul 2013 03:09:55 -0500
User-agent: KMail/4.10.5 (Linux/3.10.1-pf+; KDE/4.10.5; x86_64; ; )

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 <address@hidden>

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 "address@hidden"; 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 "address@hidden"; 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 ' "address@hidden"); 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 ' "address@hidden")
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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]