BASH: Evaluate $ 1 before string to call array length
I wrote this function here: I want to run it like this: choose "title";
function choose {
echo $1;
randNum=$RANDOM
let "numChoices=${#$1[@]}";
let "num=$randNum%$numChoices";
i=0;
while [ $i -lt $numChoices ]; do
if [ $i -eq $num ]; then
echo ${$1[$i]};
break;
fi
((i++));
done
}
When it starts, I want the final product to be identical to this one: (replace all $ 1 with the header)
function choose {
echo title;
randNum=$RANDOM
let "numChoices=${#title[@]}";
let "num=$randNum%$numChoices";
i=0;
while [ $i -lt $numChoices ]; do
if [ $i -eq $num ]; then
echo ${title[$i]};
break;
fi
((i++));
done
}
However, all I get is this error:
notify.sh: line 67: numChoices=${#$1[@]}: bad substitution
After doing a bit of documentation work, I was unable to understand substitutions and pointers and references well enough. Can anyone help with some understanding and maybe fix my syntax?
source to share
If you were using bash 4.3 you can use nameref variables like: (I've also updated various parts of your script.)
choose() {
echo $1
# This makes theVar an alias ("nameref") to the variable whose name is in $1
# Like any declare inside a function, it is implicitly local.
declare -n theVar=$1
local randNum=$RANDOM
local numChoices=${#theVar[@]}
local num=$(( randNum % numChoices ))
local i
for (( i=0; i < numChoices; ++i )); do
if (( i == num )); then
echo "${theVar[i]}"
break;
fi
done
}
But you probably don't have bash 4.3, as it's less than a year old and most distributions are very conservative about bash updates. Thus, you need to use the old style syntax syntax ${!name}
. Unfortunately this is inconvenient for array references because you need to make sure to name
include the complete index expression. And worse, as far as I know, it doesn't handle array length at all (or, for that matter, scalar length). You can get the length of the array with eval
, but my overall bias against eval leads to an alternative implementation below:
choose() {
echo $1
local randNum=$RANDOM
# For the indirection, we need to construct the indexed name.
local name=$1[@]
# This hack makes varSize a row of dots with one dot per element.
local varSize=$(printf ".%.0s" "${!name}")
local numChoices=${#varSize}
local num=$(( randNum % numChoices ))
local i
for (( i=0; i < numChoices; ++i )); do
if (( i == num )); then
# Again, we need to construct the complete indexed name.
name=$1[$i]
echo "${!name}";
break;
fi
done
}
source to share