3
#!/usr/bin/bash ARGENT=("Nous devons économiser de l'argent." "Je dois économiser de l'argent.") BIENETRE=("Comment vas-tu?" "Tout va bien ?") aoarrs=("${ARGENT}" "${BIENETRE}") select arr in "${aoarrs[@]}"; do for el in "${arr[@]}"; do echo "$el" done break done 

I want this script to print the array names to the user, ARGENT and BIENETRE, so that the user can select one of them. After the user's input the script is meant to print every element of an array selected. I want to select with select an array to loop through from an array of arrays (aoarrs). The reason why I want to use select is because in the real world my array of arrays may have many more than just two arrays in it. How might I accomplish that?

3
  • Note that ${ARGENT} is the same as ${ARGENT[0]}CommentedAug 28, 2023 at 13:01
  • You really don't want to use CAPS for shell variable names. Since, by convention, global environment variables are capitalized, it is bad practice to also use caps for your own, local variables because this can lead to naming collisions and hard to find bugs.
    – terdon
    CommentedAug 28, 2023 at 15:57
  • Please paste your script at shellcheck.net.
    – Cyrus
    CommentedAug 28, 2023 at 23:15

4 Answers 4

6

You'll store the array names in aoarrs, and inside the select body declare a nameref to the chosen name:

ARGENT=("Nous devons économiser de l'argent." "Je dois économiser de l'argent.") BIENETRE=("Comment vas-tu?" "Tout va bien ?") aoarrs=(ARGENT BIENETRE) PS3='Which array? ' select arr in "${aoarrs[@]}"; do [[ $arr ]] || continue declare -n ref=$arr for i in "${!ref[@]}"; do printf '%d\t%s\n' $i "${ref[i]}" done break done 

Running might look like

1) ARGENT 2) BIENETRE Which array? 3 Which array? 4 Which array? 5 Which array? 2 0 Comment vas-tu? 1 Tout va bien ? 
    2

    You'd want a mapping of "keys" to "values", where "values" are the lists of strings, and the "keys" are ARGENT, BIENETRE

    You're on the right path with aoarrs, because you could use that array as associative array:

    declare -A aoarrs aoarrs[ARGENT]=$ARGENT aoarrs[BIENETRE]=$BIENETRE 

    and then just iterate over all keys in that array using something like for key in ${!aoarrs[@]}….

    Sadly, bash doesn't, for whatever reason, allow lists to be elements of these associative arrays.

    So, things suck. You can for example join the elements of your lists with a reserved character to split them later on (that's stupid because it means you can't have all characters in your string, or need to start escaping them), or you build your own functions that take lists of strings, append them to a large array and then you implement your own associative lookup function on that container (that would be stupid; not only would it be slow, it would also require you to write relatively much code in a relatively ill-suited language). It would look terrible. Here's an example which I write down without testing it, because it's ugly enough that I need to get it out of my head, but don't want to deal with it any further:

    #!/bin/bash ############################### # UNTESTED STUFF # # If you think this code is # # acceptable, consider doing # # something good for yourself # # once in a while # ############################### declare -A index declare -A lengths declare -a storage # Adds an entry to our our custom container # # Usage: # add_key_values KEY "list element 1" "list element 2" … function add_key_values() { local key="$1" shift local -n valuelist=$@ # get the length of the passed list, to save it local lengthlist=${#valuelist[@]} # get the end of the current storage, that's where we start adding # our list local start_index=${#storage[@]} # finally, actually store the list items in the storage for item in "${valuelist[@]}"; do storage+=("${item}") done lengths["${key}"]=$lengthlist index["${key}"]=$start_index } # Retrieve a list from the storage # Sadly, bash is not a proper programming language, because what it calls # "functions" don't do the one thing that a function needs to do: # return a value for an argument. There's just no "return" mechanism in bash. # # Returns an empty list if the key wasn't found. # # So, after sobbing a bit, we just say # Usage: # get_values $key # Overwrites the `ret_val` variable with the list that was saved earlier function get_values() { # prepare ret_val declare -g -a ret_val=() local key=$1 # We return (with ret_val empty) if key isn't present # frigging bash doesn't have a "is key present in associative array" function… # so this is the workaround to check whether there's $key in $index. # seriously. local teststring teststring="$(printf 'index[%s]' "${key}")" # you need quite modern bash to even get the "is defined" -v test [[ -v "${teststring}" ]] || return # let's get all the elements from storage and append them to ret_val local start=${index[$key]} local length=${lengths[$key]} for idx in $(seq $(( start - 1 )) $((start - 1 + length)) ); do ret_val+=("${storage[idx]}") done } #################### # EXAMPLE USAGE #################### add_key_values "ARGENT" "Nous devons économiser de l'argent." "Je dois économiser de l'argent." add_key_values "BIENETRE" ("Comment vas-tu?" "Tout va bien ?") for key in ${!index[@]}; do echo "the list for value $key contains" get_values "${key}" for element in ${ret_val[@]}; do echo "element: ${element}" done done 

    The next option is magic that involves "indirect addressing" of variables by name using eval. That's kind of evil, and stupid, and there's very many posts on here that hint at "if you're at that point, then maybe use a proper programming language instead of bash".

    I'd concur with that: This whole problem would literally be done in four lines of python, with the first two lines would be storing "ARGENT" and "BIENETRE" and their lists into a dict. Or really, in any other common language that's not bash (or C, for that matter), associative arrays are less bad.

      1

      Spanning array has names of variables only:

      aoarrs=(ARGENT BIENETRE) while :; do select arr in "${aoarrs[@]}" quit; do declare -n ref=$arr case $arr in quit) break 2;; *) select item in "${ref[@]}"; do echo $item break done;; esac break done done 

      declare -n ref=$arr - make a reference to the variable named by its value.
      break 2 - break 2 enclosing loops.

        1

        It would be easier in ksh93 (the shell bash tries to emulate) where associative arrays can have arrays (among other things) as values.

        #! /bin/ksh - typeset -A all=( [argent]=( "Nous devons économiser de l'argent." "Je dois économiser de l'argent." ) [bien-être]=( "Comment vas-tu?" "Tout va bien ?" ) ) select topic in "${!all[@]}"; do for sentence in "${all[$topic][@]}"; do print -r -- "$sentence" done break done 

          You must log in to answer this question.

          Start asking to get answers

          Find the answer to your question by asking.

          Ask question

          Explore related questions

          See similar questions with these tags.