The explanation for ${@/%/[key]}
is this section of the bash man page:
${parameter/pattern/string}
The pattern is expanded to produce a pattern just as in pathname
expansion. Parameter is expanded and the longest match of pat-
tern against its value is replaced with string. If Ipattern
begins with /, all matches of pattern are replaced with string.
Normally only the first match is replaced. If pattern begins
with #, it must match at the beginning of the expanded value of
parameter. If pattern begins with %, it must match at the end
of the expanded value of parameter. If string is null, matches
of pattern are deleted and the / following pattern may be omit-
ted. If parameter is @ or *, the substitution operation is
applied to each positional parameter in turn, and the expansion
is the resultant list. If parameter is an array variable sub-
scripted with @ or *, the substitution operation is applied to
each member of the array in turn, and the expansion is the
resultant list.
Specifically the bit about %
there in the middle. So ${@/%/[key]}
is matching the end of the string for each value in the array and appending [key]
to it.
Assuming a call to isSubset
of isSubset a b
where a='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
and b='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [6]="6" [7]="7" [8]="8" [9]="9" [10]="10")'
. What happens in isSubset
goes like this:
isSubset() {
local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
Interpolate $1
and $2
into the above line and we get
local -a 'xkeys=("${!a[@]}")' 'ykeys=("${!b[@]}")'
Which expands to (via ${!arr[@]}
array index expansion)
local -a 'xkeys=(0 1 2 3 4 5)' 'ykeys=(0 1 2 3 4 5 6 7 8 9 10)'
At this point we now have xkeys
and ykeys
arrays of the keys of the arrays passed in.
set -- "${@/%/[key]}"
Recall that $@
is @=(a b)
and from the man page snippet above we know that this becomes
set -- 'a[key]' 'b[key]'
set --
then sets the functions positional parameters for us so we have @=('a[key]' 'b[key]')
(( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1
If xkeys
is larger than ykeys
then it can't be a subset so bail out. (This could be done before the set --
line and that would be slightly more efficient I imagine, though unlikely to matter in any but the hottest of loops.)
local key
for key in "${xkeys[@]}"; do
Loop over every key in xkeys
(with the value assigned to key
). (Note the variable name here, it is crucial.)[1]
[[ ${!2+_} && ${!1} == ${!2} ]] || return 1
More indirection, this time on the positional parameters. The above expands to
[[ ${b[key]+_} && ${a[key]} == ${b[key]} ]] || return 1
${b[key]+_}
expands to _ if b[key]
has a value and an empty string if it does not. (I'm not sure why this bothers with the alternate value expansion instead of just using ${!2}
but there is probably a reason. It could be safety in the face of set -u
or it could be safety against [[
interpreting the resulting string, though I don't think it does that but [
would have.) This test therefore passes when b[key]
has a value and fails when it does not.
${a[key]} == ${b[key]}
tests that the value in that index is the same in both arrays and the whole expression returns failure from the function when either part fails.
@danadam correctly explains, and it makes sense to include here as well, that the key detail here is that the []
indexing in array lookups does variable expansion so a[key]
in positional parameter one is not looking for the "key" index in array a
but is rather a[$key]
.
done
}
I hope that all made sense. (And I hope I got it all right. =)
- I really wanted to write "Note the variable name here, it is ... key." but the ensuing confusion wasn't worth the joke.