Size: 2988
Comment: explain
|
Size: 4506
Comment: unset / test with BASH_COMPAT=51
|
Deletions are marked like this. | Additions are marked like this. |
Line 67: | Line 67: |
== test -v and unset == Example: Filter and uniqify an array. {{{#!highlight bash function purify_cows { typeset -n _ref=$1 typeset IFS x=$2 typeset -A set set -- "${_ref[@]@Q}" eval -- "set+=(${*/*/[&]=})" if BASH_COMPAT=51 test -v 'set[$x]'; then BASH_COMPAT=51 IFS= command -- unset -v -- 'set[${x}${IFS[BASH_COMPAT=${BASH_VERSINFO[*]::2},0]}]' _ref=("${!set[@]}") else return 1 fi } function _main { set -x typeset -a cows=(moo moo moooo! moo oom moo @ @ moo) purify_cows cows @ printf '%s ' "${cows[@]}" { BASH_XTRACEFD=3 echo; } 3>/dev/null } _main EOF9 + cows=('moo' 'moo' 'moooo!' 'moo' 'oom' 'moo' '@' '@' 'moo') + typeset -a cows + purify_cows cows @ + typeset -n _ref=cows + typeset IFS x=@ + typeset -A set + set -- ''\''moo'\''' ''\''moo'\''' ''\''moooo!'\''' ''\''moo'\''' ''\''oom'\''' ''\''moo'\''' ''\''@'\''' ''\''@'\''' ''\''moo'\''' + eval -- 'set+=(['\''moo'\'']= ['\''moo'\'']= ['\''moooo!'\'']= ['\''moo'\'']= ['\''oom'\'']= ['\''moo'\'']= ['\''@'\'']= ['\''@'\'']= ['\''moo'\'']=)' ++ set+=(['moo']= ['moo']= ['moooo!']= ['moo']= ['oom']= ['moo']= ['@']= ['@']= ['moo']=) + BASH_COMPAT=51 + test -v 'set[$x]' + BASH_COMPAT=51 + IFS= + command -- unset -v -- 'set[${x}${IFS[BASH_COMPAT=${BASH_VERSINFO[*]::2},0]}]' + _ref=("${!set[@]}") + printf '%s ' oom 'moooo!' moo oom moooo! moo }}} |
This is how array variable subscripts should expand. The demo code should run in bash or zsh. Array variables work similarly in mksh and most versions of ksh93 until recently. They have some bugs and missing features.
1 #!/usr/bin/env bash
2 if [[ -v ZSH_VERSION ]]; then
3 emulate ksh
4 zmodload zsh/system
5 elif [[ -v BASH_VERSION ]]; then
6 shopt -s lastpipe extglob expand_aliases
7 shopt -u {assoc,array}_expand_once 2>/dev/null
8 enable -f fdflags{,}
9 enable -f tee{,}
10 fi
11
12 function p {
13 printf ' %s ' "$1" >&3
14 IFS= read -rd "" t <&4
15 printf '%d\0' "$((t+1))" | tee /proc/self/fd/3 >&5
16 }
17
18 function f {
19 typeset -a a
20 typeset fd f='a[$(p x)0]'
21 {
22 if [[ -v BASH_VERSION ]]; then fdflags -s +nonblock 4 5
23 elif [[ -v ZSH_VERSION ]]; then sysopen -ro nonblock -u 4 /proc/self/fd/4; sysopen -wao nonblock -u 5 /proc/self/fd/5
24 fi
25 printf '%s\0' -1 >&5
26 (( $(p a)a[\$(p b)f$(p c)]$(p d) ))
27 } 3>&1 4<<<'' 4<$(flock 4)<(flock -w 1 4) 5>/proc/self/fd/4
28 echo
29 }
30
31 function main {
32 set +m
33 typeset BASH_COMPAT=51
34 f
35 unset -v BASH_COMPAT
36 f
37 echo
38 }
39 main
40 typeset -u SHELL=$SHELL
41 typeset -p "${SHELL##*/}_VERSION"
a 0 c 1 d 2 b 3 x 4 a 0 c 1 d 2 b 3 x 4 typeset ZSH_VERSION=5.9 a 0 c 1 d 2 b 3 x 4 a 0 c 1 d 2 b 3 x 4 declare -- BASH_VERSION="5.1.16(1)-release" a 0 c 1 d 2 b 3 x 4 a 0 c 1 d 2/proc/self/fd/9: line 25: $(p b)f: arithmetic syntax error: operand expected (error token is "$(p b)f") declare -- BASH_VERSION="5.3.0(1)-devel"
a, c, and d are never seen by the arithmetic expression. They are pre-expanded because they are unescaped and thus evaluated first from left-to-right before the arithmetic evaluation stage. Because p produces nothing on stdout the overall expression is effectively reduced to a[\$(p b)f].
Next the arithmetic expression encounters the array variable a which must call the variable resolver with the variable name including its subscript. The variable resolver now performs expansions left-to-right on the subscript, evaluating b which again produces no stdout and effectively reduces the overall expression to a[f]. Now the variable resolver calls the arithmetic evaluator again to evaluate the index which has been reduced to just f.
Because f is a string that is not a valid integer literal, bash evaluates its value as an arithmetic expression so we now evaluate a[$(p x)0]. Thus x is the final command substitution to be evaluated and the overall expression is effectively reduced to a[a[0]]. The array variable a is a dummy variable with no elements so the result of the expression is 0 and all of the output was produced as a side-effect of evaluating the command substitutions.
test -v and unset
Example: Filter and uniqify an array.
1 function purify_cows {
2 typeset -n _ref=$1
3 typeset IFS x=$2
4 typeset -A set
5 set -- "${_ref[@]@Q}"
6 eval -- "set+=(${*/*/[&]=})"
7 if BASH_COMPAT=51 test -v 'set[$x]'; then
8 BASH_COMPAT=51 IFS= command -- unset -v -- 'set[${x}${IFS[BASH_COMPAT=${BASH_VERSINFO[*]::2},0]}]'
9 _ref=("${!set[@]}")
10 else
11 return 1
12 fi
13 }
14
15 function _main {
16 set -x
17 typeset -a cows=(moo moo moooo! moo oom moo @ @ moo)
18 purify_cows cows @
19 printf '%s ' "${cows[@]}"
20 { BASH_XTRACEFD=3 echo; } 3>/dev/null
21 }
22
23 _main
24
25 EOF9
26
27 + cows=('moo' 'moo' 'moooo!' 'moo' 'oom' 'moo' '@' '@' 'moo')
28 + typeset -a cows
29 + purify_cows cows @
30 + typeset -n _ref=cows
31 + typeset IFS x=@
32 + typeset -A set
33 + set -- ''\''moo'\''' ''\''moo'\''' ''\''moooo!'\''' ''\''moo'\''' ''\''oom'\''' ''\''moo'\''' ''\''@'\''' ''\''@'\''' ''\''moo'\'''
34 + eval -- 'set+=(['\''moo'\'']= ['\''moo'\'']= ['\''moooo!'\'']= ['\''moo'\'']= ['\''oom'\'']= ['\''moo'\'']= ['\''@'\'']= ['\''@'\'']= ['\''moo'\'']=)'
35 ++ set+=(['moo']= ['moo']= ['moooo!']= ['moo']= ['oom']= ['moo']= ['@']= ['@']= ['moo']=)
36 + BASH_COMPAT=51
37 + test -v 'set[$x]'
38 + BASH_COMPAT=51
39 + IFS=
40 + command -- unset -v -- 'set[${x}${IFS[BASH_COMPAT=${BASH_VERSINFO[*]::2},0]}]'
41 + _ref=("${!set[@]}")
42 + printf '%s ' oom 'moooo!' moo
43 oom moooo! moo