Differences between revisions 2 and 3
Revision 2 as of 2023-08-03 08:20:22
Size: 2988
Editor: ormaaj
Comment: explain
Revision 3 as of 2023-10-19 23:53:17
Size: 4506
Editor: ormaaj
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


CategoryExampleCode

ArrayVariables (last edited 2023-10-19 23:56:50 by ormaaj)