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?

+3


source to share


1 answer


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
}

      

+4


source







All Articles