How to iterate over list of dictionaries in bash - 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

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.

Open file with two columns and dynamically create variables

I'm wondering if anyone can help. I've not managed to find much in the way of examples and I'm not sure where to start coding wise either.
I have a file with the following contents...
VarA=/path/to/a
VarB=/path/to/b
VarC=/path/to/c
VarD=description of program
...
The columns are delimited by the '=' and some of the items in the 2nd column may contain gaps as they aren't just paths.
Ideally I'd love to open this in my script once and store the first column as the variable and the second as the value, for example...
echo $VarA
...
/path/to/a
echo $VarB
...
/path/to/a
Is this possible or am I living in a fairy land?
Thanks
You might be able to use the following loop:
while IFS== read -r name value; do
declare "$name=$value"
done < file.txt
Note, though, that a line like foo="3 5" would include the quotes in the value of the variable foo.
A minus sign or a special character isn't allowed in a variable name in Unix.
You may consider using BASH associative array for storing key and value together:
# declare an associative array
declare -A arr
# read file and populate the associative array
while IFS== read -r k v; do
arr["$k"]="$v"
done < file
# check output of our array
declare -p arr
declare -A arr='([VarA]="/path/to/a" [VarC]="/path/to/c" [VarB]="/path/to/b" [VarD]="description of program" )'
What about source my-file? It won't work with spaces though, but will work for what you've shared. This is an example:
reut#reut-home:~$ cat src
test=123
test2=abc/def
reut#reut-home:~$ echo $test $test2
reut#reut-home:~$ source src
reut#reut-home:~$ echo $test $test2
123 abc/def

Bash 4 Generate Associative Array

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

Got stuck in dynamically allocating an associative array in bash

I try to create an associative array in bash like this:
#! /bin/bash
declare -a arr
while read key
do
arr+=([$key]=1)
done < /dev/stdin
for i in ${!arr[#]}
do
echo "key:$i,value:${arr[$i]}"
done
I have inputed "leopard" and "longtuan" as a key,the output supposed to be like this in my mind:
key:leopard,value:1
key:longtuan,value:1
But I only got:
key:0,value:11
How can I get through of this problem, Thanks for any help.
You're creating a regular array not an associative array. Change this:
declare -a arr
into this:
declare -A arr
and the problem will disappear.
Associative arrays are declared with capital A:
declare -A arr
declare -a declares a regular array.

Resources