Bash 4 Generate Associative Array - bash

I'm trying to create a nice neat table from output. Here's the code that working off of now:
vmnumber=0
declare -A vm_array=()
for vm in $vms; do
declare vm_array[$vmnumber][cpu]="10"
echo "Test ${vm_array[$vmnumber][cpu]}"
declare vm_array[$vmnumber][memory]="20"
declare vm_array[$vmnumber][diskspace]="20"
vmnumber=$(($vmnumber + 1))
done
# Table output
for((i=0;i<$vmnumber;i++)); do
printf "%10s %10s %10s" vm_array[$i][cpu] vm_array[$i][memory] vm_array[$i][diskspace]"
done
Echo is not working and only the variable names come out in printf.
How can I generate an associative array of out of these values?

Associative arrays support non-numeric keys. They are not multidimensional, and cannot have other arrays as your values. For your use case, use a separate non-associative array per VM, as follows:
# Why this is this associative at all, if your keys are numeric?
vmnumber=0
vm_cpu=() vm_memory=() vm_diskspace=( )
for vm in $vms; do ## aside: don't use for this way
vm_cpu[$vmnumber]="10"
vm_memory[$vmnumber]="20"
vm_diskspace[$vmnumber]="20"
(( ++vmnumber ))
done
# test output
declare -p vm_cpu vm_memory vm_diskspace
# Table output
for i in "${!vm_cpu[#]}"; do # iterate over keys
printf '%10s %10s %10s\n' "${vm_cpu[$i]}" "${vm_memory[$i]}" "${vm_diskspace[$i]}"
done
If you really want a single associative array, that would look like the following:
vmnumber=0
declare -A vm_data=( )
for vm in $vms; do ## aside: don't use for this way
vm_data[cpu_$vmnumber]="10"
vm_data[memory_$vmnumber]="20"
vm_data[diskspace_$vmnumber]="20"
(( ++vmnumber ))
done
# test output
declare -p vm_data
# Table output
for ((i=0; i<vmnumber; i++)); do
printf '%10s %10s %10s\n' "${vm_data[cpu_$i]}" "${vm_data[memory_$i]}" "${vm_data[diskspace_$i]}"
done

Related

Dynamically creating associative arrays in bash

I have a variable ($OUTPUT) that contains the following name / value pairs:
member_id=4611686018429783292
platform=Xbox
platform_id=1
character_id=2305843009264966985
period_dt=2020-11-25 20:31:14.923158 UTC
mode=all Crucible modes
mode_id=5
activities_entered=18
activities_won=10
activities_lost=8
assists=103
kills=233
average_kill_distance=15.729613
total_kill_distance=3665
seconds_played=8535
deaths=118
average_lifespan=71.72269
total_lifespan=8463.277
opponents_defeated=336
efficiency=2.8474576
kills_deaths_ratio=1.9745762
kills_deaths_assists=2.411017
suicides=1
precision_kills=76
best_single_game_kills=-1
Each line ends with \n.
I want to loop through them, and parse them into an associative array, and the access the values in the array by the variable names:
while read line
do
key=${line%%=*}
value=${line#*=}
echo $key=$value
data[$key]="$value"
done < <(echo "$OUTPUT")
#this always prints the last value
echo ${data['seconds_played']}
This seems to work, i.e. key/value print the right values, but when I try to pull any values from the array, it always returns the last value (in this case -1).
I feel like im missing something obvious, but have been banging my head against it for a couple of hours.
UPDATE: My particular issue is I'm running a version of bash (3.2.57 on OSX) that doesn't support associative arrays). I'll mark the correct answer below.
Without declare -A data, then data is a normal array. In normal arrays expressions in [here] first undergo expansions, then arithmetic expansion. Inside arithmetic expansion unset variables are expanded to 0. You are effectively only just setting data[0]=something, because data[$key] is data[seconds_played] -> variable seconds_played is not defined, so it expands to data[0]
Add declare -A data and it "should work". You could also just:
declare -A data
while IFS== read -r key value; do
data["$key"]="$value"
done <<<"$OUTPUT"
Try declaring data as an associative array before populating it, eg:
$ typeset -A data # declare as an associative array
$ while read line
do
key=${line%%=*}
value=${line#*=}
echo $key=$value
data[$key]="$value"
done <<< "${OUTPUT}"
$ typeset -p data
declare -A data=([mode]="all Crucible modes" [period_dt]="2020-11-25 20:31:14.923158 UTC" [deaths]="118" [best_single_game_kills]="-1" [efficiency]="2.8474576" [precision_kills]="76" [activities_entered]="18" [seconds_played]="8535" [total_lifespan]="8463.277" [average_lifespan]="71.72269" [character_id]="2305843009264966985" [kills]="233" [activities_won]="10" [average_kill_distance]="15.729613" [activities_lost]="8" [mode_id]="5" [assists]="103" [suicides]="1" [total_kill_distance]="3665" [platform]="Xbox" [kills_deaths_ratio]="1.9745762" [platform_id]="1" [kills_deaths_assists]="2.411017" [opponents_defeated]="336" [member_id]="4611686018429783292" )
$ echo "${data['seconds_played']}"
8535

Adding to Bash associative arrays inside functions

I'm trying to use associative arrays as a work around for Bash's poor function parameter passing. I can declare a global associative array and read/write to that but I would like to have the variable name passed to the function since many times I want to use the same function with different parameter blocks.
Various Stack Overflow posts have approaches for reading a passed array within a function but not writing to it to allow return values. Pseudo Bash for what I'm trying to do is thus:
TestFunc() {
local __PARMBLOCK__=${1} # Tried ${!1} as well
# Do something with incoming array
__PARMBLOCK__[__rc__]+=1 # Error occured
__PARMBLOCK__[__error__]+="Error in TestFunc"
}
declare -A FUNCPARM
# Populate FUNCPARM
TestFunc FUNCPARM
if [[ ${FUNCPARM[__rc__]} -ne 0 ]]; then
echo "ERROR : ${FUNCPARM[__error__]}
fi
Is this kind of thing possible or do I really need to abandon Bash for something like Python?
EDIT: Found the duplicate. This is basically the same answer as this one.
You can use a reference variable for that, see help declare:
declare [-aAfFgilnrtux] [-p] [name[=value] ...]
[...]
-n make NAME a reference to the variable named by its value
[...]
When used in a function, declare makes NAMEs local, as with the local command.
f() {
declare -n paramblock="$1"
# example for reading (print all keys and entries)
paste <(printf %s\\n "${!paramblock[#]}") <(printf %s\\n "${paramblock[#]}")
# example for writing
paramblock["key 1"]="changed"
paramblock["new key"]="new output"
}
Example usage:
$ declare -A a=(["key 1"]="input 1" ["key 2"]="input 2")
$ f a
key 2 input 2
key 1 input 1
$ declare -p a
declare -A a=(["key 2"]="input 2" ["key 1"]="changed" ["new key"]="new output" )
This works very well. The only difference to an actual associative array I found so far is, that you cannot print the referenced array using declare -p as that will only show the reference.

Bash : Remove values present in file from a variable

I have a variable which has values such as
"abc.def.ghi aa.bbb.ccc kk.lll.mmm ppp.qqq.lll"
and a file which has values
kk.lll.mmm
abc.def.ghi
I want to remove these values from the variable. File and variable has some values similar but not in same order.
If you don't care about ordering, one of the cleanest ways to maintain a set is with an associative array. This gives you O(1) ability to check for or remove items -- much better performance than you'd get with sed
## Convert from a string to an array
str="abc.def.ghi aa.bbb.ccc kk.lll.mmm ppp.qqq.lll"
read -r -a array <<<"$str"
## ...and from there to an *associative* array
declare -A items=( )
for item in "${array[#]}"; do
items[$item]=1
done
## ...whereafter you can remove items in O(1) time
while IFS= read -r line; do
unset "items[$item]"
done <file
## Write list of remaining items
printf 'Remaining item: %q\n' "${!items[#]}"
By the way, much of this code could be skipped if the original data were in associative-array from to start with:
# if the assignment looked like this, could just start at the "while read" loop.
declare -A items=( [abc.def.ghi]=1 [aa.bbb.ccc]=1 [kk.lll.mmm]=1 [ppp.qqq.lll]=1 )

Loop over two associative arrays in Bash

Say I have two associative arrays in Bash
declare -A a
declare -A b
a[xz]=1
b[xz]=2
a[zx]=3
b[zx]=4
I want to do something like this
for arr in ${a[#]} ${b[#]}; do echo ${arr[zx]}; done
and get 3 and 4 in output
but I get
$ for arr in ${a[#]} ${b[#]}; do echo ${arr[zx]}; done
1
3
2
4
Is there a way to do this in Bash?
You don't want to iterate over the contents; you want to iterate over the names of the arrays, then use indirect expansion to get the desired value of the fixed key from each array.
for arr in a b; do
t=$arr[zx] # first a[zx], then b[zx]
printf '%s\n' "${!t}"
done
Here, the variable "name" for use in indirect expansion is the name of the array along with the desired index.
Assuming the keys in both the arrays match(a major assumption), you can use one array as reference and loop over the keys and print in each array.
for key in "${!a[#]}"; do
printf "Array-1(%s) %s Array-2(%s) %s\n" "$key" "${a[$key]}" "$key" "${b[$key]}"
done
which produces an output as below. You can of-course remove the fancy debug words(Array-1, Array-2) which was added just for an understanding purpose.
Array-1(xz) 1 Array-2(xz) 2
Array-1(zx) 3 Array-2(zx) 4
One general good practice is always quote (for key in "${!a[#]}") your array expansions in bash, so that the elements are not subjected to word-splitting by the shell.

How to iterate over list of dictionaries in bash

I am currently learning shell scripting and need your help!
> array = [{u'name': u'androidTest', u'arn': u'arn:XXX', u'created':
1459270137.749}, {u'name': u'android-provider2016-03-3015:23:30', u'arn':XXXXX', u'created': 1459365812.466}]
I have a list of dictionary and want to extract the arn value from the dictionary. In python it is pretty simple for example:
for project in array:
print project['arn']
How will I write the equivalent loop in bash? If I try something like this, it is not working:
for i in "$array"
do
echo $i['arn']
done
The suggested duplicate is for associative arrays, not a list of associative arrays.
Bash can't nest data structures, so the thing that would correspond to the list of dictionaries doesn't exist natively. Using a relatively recent Bash (4.3 or newer), we can achieve something similar with namerefs:
# Declare two associative arrays
declare -A arr1=(
[name]='androidTest'
[arn]='arn:XXX'
[created]='1459270137.749'
)
declare -A arr2=(
[name]='android-provider2016-03-3015:23:30'
[arn]='XXXXX'
[created]='1459365812.466'
)
# Declare array of names of associative arrays
names=("${!arr#}")
# Declare loop variable as nameref
declare -n arr_ref
# Loop over names
for arr_ref in "${names[#]}"; do
# Print value of key 'arn'
printf "%s\n" "${arr_ref[arn]}"
done
This returns
arn:XXX
XXXXX
for project in $array
do
echo $project{'arn'}
done
Or in one line:
for project in $array; do echo $project{'arn'}; done

Resources