Home > Back-end >  Redefine a Bash variable in a loop
Redefine a Bash variable in a loop

Time:12-04

I'd like to create a Bash dictionary in a loop, then add it to an array:

#!/usr/bin/env bash

periods=()

for arg
do
    echo "arg: $arg"

    day= month= year=

    case $arg in
      */*/* | *-*-*)
          read -r year month day < <(date ' %Y %m %d' -d "$arg")
          ;;
      ????[-/]?? | ????[-/]?)
          IFS='-/' read -r year month <<< "$arg"
          ;;
      ??[-/]???? | ?[-/]????)
          IFS='-/' read -r month year <<< "$arg"
          ;;
    esac

    declare -A period

    period[year]=$year 
    period[month]=$month 
    period[day]=$day

    periods =(period)

done

# display
for period in "${!periods[@]}"
do 
    echo "year: ${period[year]} / month: ${period[month]} / day: ${period[day]}"
done

Unfortunately, it appears each period instance in periods is a reference to the last-added value:

$ ./parse.sh 3/14/19 3/14/2020
arg: 3/14/19
arg: 3/14/2020
year: 2020 / month: 03 / day: 14
year: 2020 / month: 03 / day: 14

Is there a way to define a Bash variable such that each instance is unique?

CodePudding user response:

bash doesn't have data structure to support 2 dimensional arrays or a list of associative arrays as you are trying in this code, however you can use this simulation of 2 dimensional arrays in your bash code:

CODE DEMO

#!/usr/bin/env bash

declare -A periods
rec=0

for arg; do
    echo "arg: $arg"
    day= month= year=

    case $arg in
      */*/* | *-*-*)
          read -r year month day < <(date ' %Y %m %d' -d "$arg")
          ;;
      ????[-/]?? | ????[-/]?)
          IFS='-/' read -r year month <<< "$arg"
          ;;
      ??[-/]???? | ?[-/]????)
          IFS='-/' read -r month year <<< "$arg"
          ;;
    esac

    periods[$rec,year]="$year"
    periods[$rec,month]="$month"
    periods[$rec,day]="$day"
    ((rec  ))
done
# check periods array content
declare -p periods

# display values from associative array
for ((i=0; i<rec; i  )); do 
    echo "year: ${periods[$i,year]} / month: ${periods[$i,month]} / day: ${periods[$i,day]}"
done

Output:

arg: 3/14/19
arg: 3/14/2020
declare -A periods=([0,year]="2019" [1,day]="14" [0,month]="03" [0,day]="14" [1,year]="2020" [1,month]="03" )
year: 2019 / month: 03 / day: 14
year: 2020 / month: 03 / day: 14

CodePudding user response:

Depending on how hacky you wanna get, you can "trick" Bash into supporting arrays of associative arrays, though at this point I'd start to think of other languages I'd want to write this script in ;)

The gist of this is you're basically saving the structure of the associative array as a string in the array, keeping declares sandboxed from each other via functions, then evaling each assoc array back to a real bash data structure when you're ready to read from it.

This method is useful for "passing" associative arrays to functions, as well.

#!/usr/bin/env bash

periods=()

function main {
  for arg
  do
      echo "arg: $arg"

      day= month= year=

      case $arg in
        */*/* | *-*-*)
            read -r year month day < <(date ' %Y %m %d' -d "$arg")
            ;;
        ????[-/]?? | ????[-/]?)
            IFS='-/' read -r year month <<< "$arg"
            ;;
        ??[-/]???? | ?[-/]????)
            IFS='-/' read -r month year <<< "$arg"
            ;;
      esac

      declare -A period

      period[year]="${year}"
      period[month]="${month}"
      period[day]="${day}"

      save_to_array "period"

  done
}

function save_to_array {
  var=$(declare -p "$1")
  periods =("${var}")
}

main "$@"

# display
for _per in "${periods[@]}" # note the lack of ! expansion
do
  eval "declare -A final="${_per#*=} # new associative array `final` gets declared here
  echo "year: ${final[year]} / month: ${final[month]} / day: ${final[day]}"
done
  •  Tags:  
  • bash
  • Related