-
-
Save dimo414/2fb052d230654cc0c25e9e41a9651ebe to your computer and use it in GitHub Desktop.
#!/bin/bash | |
echo "Bash Version: $BASH_VERSION" | |
pass_fail() { | |
#printf '%s:%s' "$1" "$2"; return # diagnostic | |
if (( $1 == $2 )); then | |
printf '\e[32m%s\e[0m' "✓" | |
else | |
printf '\e[31m%s\e[0m' "✗" | |
fi | |
} | |
test_expansion() ( | |
readonly arr=("$@") | |
set -u | |
printf '$#:%s' "$#" | |
copy=( "${@:+"$@"}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( "${@:+$@}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( "${@+"$@"}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( "${@+$@}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( ${@:+"$@"} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( ${@:+$@} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( ${@+"$@"} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( ${@+$@} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( "${@:1}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
copy=( ${@:1} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "$#")" | |
printf '\n' | |
printf '#arr:%s' "${#arr[@]}" | |
copy=( "${arr[@]:+"${arr[@]}"}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( "${arr[@]:+${arr[@]}}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( "${arr[@]+"${arr[@]}"}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( "${arr[@]+${arr[@]}}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( ${arr[@]:+"${arr[@]}"} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( ${arr[@]:+${arr[@]}} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( ${arr[@]+"${arr[@]}"} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
copy=( ${arr[@]+${arr[@]}} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" | |
( copy=( "${arr[@]:0}" ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" ) 2>/dev/null || printf '\t\e[1;33m%s\e[0m' "!" | |
( copy=( ${arr[@]:0} ); printf '\t%s' "$(pass_fail "${#copy[@]}" "${#arr[@]}")" ) 2>/dev/null || printf '\t\e[1;33m%s\e[0m' "!" | |
printf '\n' | |
) | |
printf '\t%s' '":+"' '":+' '"+"' '"+' ':+"' ':+' '+"' '+' '":0/1' ':0/1' | |
printf '\n' | |
test_expansion | |
test_expansion '' | |
test_expansion '' '' | |
test_expansion a 'b c' |
Don't worry, ${arr[@]+"${arr[@]}"}
will not have word-splitting applied, even though it is not surrounded by " … "
(as you would normally do with "${arr[@]}"
, and as is done with the inferior "${arr[@]+"${arr[@]}"}"
).
Here's a test:
arr=(a 'b c');
for a in ${arr[@]+"${arr[@]}"}; do
echo ">$a<";
done
The output is:
>a<
>b c<
@quinncomendant yes, that's exactly what this post is demonstrating. But ${arr[@]+"${arr[@]}"}
is quoted, which is why word splitting doesn't occur. "${arr[@]+"${arr[@]}"}"
("+"
in the table above) does not work correctly in some versions of bash, namely 4.2 and 4.3.
@dimo414 Great stuff! But isn't the reference to ${@+"$@"}
excessive? I've modified your script with '"$@"'
included as a test case and it reports no issue on any of the bash versions listed. When would the longer form be required?
@andrewgdotcom can you share a more complete example of what you tried? As written, '"$@"'
with enclosing single quotes would be a single string of the literal characters "$@"
rather than an array expansion.
@dimo414 I updated your script to read:
#!/bin/bash
echo "Bash Version: $BASH_VERSION"
expansion() {
local incantation=$1 expected=$2 actual
shift 2
actual=$(
set -u
eval "$(printf 'copy=( %s ); printf "${#copy[@]}"' "$incantation")" 2>/dev/null
) || { printf '\t\e[1;33m%s\e[0m' "!"; return 0; }
if (( actual == expected )); then
printf '\t\e[32m%s\e[0m' "✓"
else
printf '\t\e[31m%s\e[0m' "✗"
fi
}
test_expansion() {
local arr=("$@")
printf '$#:%s' "$#"
for incantation in \
'$@' '"$@"' \
'"${@:+"$@"}"' '"${@:+$@}"' '"${@+"$@"}"' '"${@+$@}"' '${@:+"$@"}' '${@:+$@}' \
'${@+"$@"}' '${@+$@}' '"${@:1}"' '${@:1}'
do
expansion "$incantation" "$#" "$@"
done
printf '\n'
printf '#arr:%s' "${#arr[@]}"
for incantation in \
'${arr[@]}' '"${arr[@]}"' \
'"${arr[@]:+"${arr[@]}"}"' '"${arr[@]:+${arr[@]}}"' '"${arr[@]+"${arr[@]}"}"' \
'"${arr[@]+${arr[@]}}"' '${arr[@]:+"${arr[@]}"}' '${arr[@]:+${arr[@]}}' \
'${arr[@]+"${arr[@]}"}' '${arr[@]+${arr[@]}}' '"${arr[@]:0}"' '${arr[@]:0}'
do
expansion "$incantation" "$#"
done
printf '\n'
}
printf '\t%s' '.' '"' '":+"' '":+' '"+"' '"+' ':+"' ':+' '+"' '+' '":0/1' ':0/1'
printf '\n'
test_expansion
test_expansion ''
test_expansion '' ''
test_expansion a 'b c'
and the output was:
Bash Version: 3.1.23(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 3.2.57(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.0.44(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.1.17(2)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.2.53(2)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ! !
$#:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.3.48(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ! ! ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ! !
$#:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.4.23(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 5.0.18(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
#arr:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
#arr:1 ✗ ✓ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
#arr:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Second column, odd lines represent "$@"
- no issues reported?
It's easier to see if you comment out the #arr
tests entirely, to leave only the $#
ones:
Bash Version: 3.1.23(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 3.2.57(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.0.44(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.1.17(2)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✓ ✓ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.2.53(2)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.3.48(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 4.4.23(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Bash Version: 5.0.18(1)-release
. " ":+" ":+ "+" "+ :+" :+ +" + ":0/1 :0/1
$#:0 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
$#:1 ✗ ✓ ✗ ✗ ✓ ✓ ✗ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
$#:2 ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✗ ✓ ✗ ✓ ✗
Ok yes I follow you now. You're right that $@
is not affected by the same set -u
issue as other arrays, I believe I just included it in the table for completeness - as you've found $@
expansion sometimes behaves differently than ${arr[@]}
expansion.
If the
eval
-magic feels a bit too handwavy for you, an earlier version of the script doesn't use it.