Search code examples
bashvariablesstring-interpolation

Declaring a variable name (not value) in bash with string substitution


I have a set of variables whose values I need to modify in a loop, if a match is found in an array which I am reading later. The array elements are substrings of the variable names, so I thought of writing a generic for loop, which would modify the variable values on the fly, attempting string substitution in the variable names, but that doesn't work. Here is the code:

CUT_APPLE=false
CUT_GUAVA=false
CUT_MANGO=false

AVAILABLE_FRUITS=("APPLE" "GUAVA")

for i in "${AVAILABLE_FRUITS[@]}"; do
CUT_$i=true
done
**CUT_APPLE=true: command not found**                               
**CUT_GUAVA=true: command not found** 

Is there any way using which I can get interpolation in variable name declaration to work?


Solution

  • If your Bash version is greater than 4, it supports associative arrays which make it easy to check whether a boolean key exists:

    #!/usr/bin/env bash
    
    # Associative array to store keys of available fruits
    declare -A cut_fruit=()
    
    declare -a available_fruits=('APPLE' 'GUAVA' 'RED BANANA')
    
    for fruit in "${available_fruits[@]}"; do
      # Create key for fruit in Associative array if available
      # shellcheck disable=SC2034 # cut_fruit is indeed used
      cut_fruit[$fruit]=
    done
    
    # Check if cut fruit is available by checking key existence
    for fruit in 'APPLE' 'BANANA' 'RED BANANA' 'MANGO'; do
      if [[ -v cut_fruit[$fruit] ]]; then
        printf 'Cut %s is available.\n' "$fruit"
      else
        printf 'There is no cut %s.\n' "$fruit"
      fi
    done
    

    Sample output:

    Cut APPLE is available.
    There is no cut BANANA.
    Cut RED BANANA is available.
    There is no cut MANGO.
    

    Here is a little game to play with associative arrays keys and boolean checks:

    #!/usr/bin/env bash
    
    print_basket() {
      # Print remaining keys (content of basket)
      for fruit in "${!basket[@]}"; do
        printf '%d %s\n' "${basket[$fruit]}" "${fruit,,}"
      done
    }
    
    cat <<'EOF'
    --:{# Guess the fruits in my basket #}:--
    EOF
    
    declare -a all_fruits=(
      'apple' 'banana' 'cherry' 'lemon' 'mango'
      'orange' 'peach' 'plum' 'red banana' 'sweat chestnut')
    # Number of fruits in the basked
    selection_size=3
    
    declare -a fruits_selection
    mapfile -t fruits_selection < <(
      # Shuffle a selection of fruits
      printf '%s\n' "${all_fruits[@]}" | shuf -n "$selection_size"
    )
    
    declare -A basket
    # Set selected fruits into basked
    for fruit in "${fruits_selection[@]}"; do
      # Uppercase the key for case insensitive match later
      basket[${fruit^^}]=$((RANDOM % 9 + 1))
    done
    
    # Cheating
    declare -p basket
    
    max_tries=3
    tries_left=$max_tries
    guessed=0
    while [ $guessed -lt $selection_size ] && [ $tries_left -gt 0 ]; do
      printf 'Enter the name of a fruit: '
      read -r fruit
      # Uppercase fruit for case insensitive match
      fruit="${fruit^^}"
      if [[ -v basket[$fruit] ]]; then
        printf 'Yes, there is %d %s in my basket!\n' \
          "${basket[$fruit]}" "${fruit,,}"
        guessed=$((guessed + 1))
        # Remove fruit from basket
        unset "basket[$fruit]"
      else
        tries_left=$((tries_left - 1))
        printf 'No, there is no %s in my basket!\nTries left: %d\n' \
          "${fruit,,}" "$tries_left"
      fi
    done
    
    case $guessed in
      "$selection_size")
        printf '* * * Congratulations, you guessed all fruilts! * * *\n'
        ;;
      0)
        printf 'Sorry you could not guess any of the fruits!\nBasket contained:\n'
        print_basket
        ;;
      *)
        printf 'Nice! You guessed %d fruits out of %s!\nUnguessed fruits:\n' \
          "$guessed" "$selection_size"
        print_basket
        ;;
    esac