Bash scripting - Iterating through "variable" variable names for a list of associative arrays - linux

I've got a variable list of associative arrays that I want to iterate through and retrieve their key/value pairs.
I iterate through a single associative array by listing all its keys and getting the values, ie.
for key in "${!queue1[#]}" do
echo "key : $key"
echo "value : ${queue1[$key]}"
done
The tricky part is that the names of the associative arrays are variable variables, e.g. given count = 5, the associative arrays would be named queue1, queue2, queue3, queue4, queue5.
I'm trying to replace the sequence above based on a count, but so far every combination of parentheses and eval has not yielded much more then bad substitution errors. e.g below:
for count in {1,2,3,4,5} do
for key in "${!queue${count}[#]}" do
echo "key : $key"
echo "value : ${queue${count}[$key]}"
done
done
Help would be very much appreciated!

The difficulty here stems from the fact that the syntax for indirect expansion (${!nameref}) clashes with the syntax for extracting keys from an associative arrays (${!array[#]}). We can have only one or the other, not both.
Wary as I am about using eval, I cannot see a way around using it to extract the keys of an indirectly referenced associative array:
keyref="queue${count}[#]"
for key in $(eval echo '${!'$keyref'}'); do ... ; done
You can however avoid eval and use indirect expansion when extracting a value from an array given the key. Do note that the [key] suffix must be part of the expansion:
valref="queue${count}[$key]"
echo ${!valref}
To put this in context:
for count in {1..5} ; do
keyref="queue${count}[#]"
for key in $(eval echo '${!'$keyref'}'); do
valref="queue${count}[$key]"
echo "key = $key"
echo "value = ${!valref}"
done
done

I was able to make it work with the following script:
for count in {1..5} ; do
for key in $(eval echo '${!q'$count'[#]}') ; do
eval echo '${q'$count"[$key]}"
done
done
Note it breaks if any key contained a space. If you want to deal with complex data structures, use a more powerful language like Perl.

I think this might work (but untested). The key is to treat the indexing
as the full name of a variable. (That is, the array queue5 can be
treated as a sequence of variables named queue5[this], queue5[that], etc.)
for count in {1,2,3,4,5} do
assoc="queue$count[#]"
for key in "${!assoc}" do
echo "key : $key"
val="queue$count[$key]"
echo "value : ${!val}"
done
done

Related

Copy a bash associative array using eval statement

Before we get into the question, I know there are answers SIMILAR to this on stack overflow already. However this one is unique in it's use of the eval statement with associative arrays. ( Believe me I've read them all ).
Okay now into the question
I have X number of arrays defined via an eval function similar to this:
for (( i=1;i<=X;i++ ))
do
eval "declare -gA old$i"
eval "old$i[key]=value"
done
This code is in function : makeArrays
Now I have a second function that must loop through these different arrays
old1
old2
.
.
.
oldX
I'll call this function : useArrays
Now, I have a for loop for this useArrays function.
for (( i=0;i<$#;i++ ))
do
// ACCESS OLD1[KEY]
done
My question is, how do I access this array FOR COMPARISONS.
I.E.
if[ old1 -eq 0 ]
then
...
fi
Is there a way I could COPY these associate arrays into a variable I can use for comparisons using eval as little as possible?
Modern versions of bash support namerefs (originally a ksh feature), so you can point a constant name at any variable you choose; this makes eval unnecessary for the purposes to which you're presently placing it.
key="the key you want to test"
for (( i=0;i<$#;i++ )); do
declare -n "oldArray=old$i" # map the name oldArray to old0/old1/old2/...
printf 'The value of %q in old%q is: %q\n' "$key" "$i" "${oldArray[$key]}"
unset -n "oldArray" # remove that mapping
done
You could of course refer to "${!oldArray[#]}" to iterate over its keys; also map a newArray namevar to compare with; etc.

Get variable name while iterating over array in bash

What I have is an array with some variables. I can iterate to get the values of those vars but what I need is actually their names (values will be used elsewhere).
Going with var[i] won't work cause I will have different names. I guess I could workaround this by creating another array with the names - something similar to this:
Getting variable values from variable names listed in array in Bash
But I'm wondering if there is a better way to do this.
var1=$'1'
var2=$'2'
var3=$'3'
Array=( $var1 $var2 $var3)
for ((i=0; i<${#Array[#]}; i++))
do
echo ${Array[i]}
done
Is:
>1
>2
>3
Should be:
>var1
>var2
>var3
It sounds like you want an associative array.
# to set values over time
declare -A Array=( ) || { echo "ERROR: Need bash 4.0 or newer" >&2; exit 1; }
Array[var1]=1
Array[var2]=2
Array[var3]=3
This can also be assigned at once:
# or as just one assignment
declare -A Array=( [var1]=1 [var2]=2 [var3]=3 )
Either way, one can iterate over the keys with "${!Array[#]}", and retrieve the value for a key with ${Array[key]}:
for var in "${!Array[#]}"; do
val="${Array[$var]}"
echo "$var -> $val"
done
...will, after either of the assignments up top, properly emit:
var1 -> 1
var2 -> 2
var3 -> 3
What about this solution?
#!/bin/bash
var1=$'1'
var2=$'2'
var3=$'3'
Array=( var1 var2 var3 )
for var in "${Array[#]}"; do
echo "$var = ${!var}"
done
The idea just consists in putting your variable names in the array, then relying on the indirection feature of Bash.
But as pointed out by #CharlesDuffy, the use of associative arrays sounds better adapted to the OP's use case.
Also, this related article may be worth reading: How can I use variable variables… or associative arrays?

Why do I get this strange output with an associative array in bash when I perform a manual bubble sort?

I have a bash associative array that looks like this
declare -A arraySalary=( [1]=1000 [8]=3000 [2]=2000)
I am learning bash scripting and am trying to implement a bubble sort on the array
with this piece of code
sortedDesc=false
while ! $sortedDesc ;
do
sortedDesc=true
for ((currentIndex=0; currentIndex<$((${#arraySalary[#]} -1)); currentIndex++))
do
if [[ ${arraySalary[$((currentIndex))]} -lt ${arraySalary[$((currentIndex + 1))]} ]]
then
sortedDesc=false
biggerNumber=${arraySalary[((currentIndex - 1))]}
arraySalary[$((currentIndex + 1))]=${arraySalary[$((currentIndex))]}
arraySalary[currentIndex]=${biggerNumber}
echo "swapped"
fi
done
done
echo "Printing new values"
# Print new values
for key in "${!arraySalary[#]}";
do
echo $key "->" ${arraySalary[$key]}
done
but the output I get is
swapped
swapped
Printing new values
0 -> 2000
1 -> 1000
2 ->
Can someone please explain why this is? Thanks
I don't know why you have chosen to use an associative array instead of an ordinary array, but you need to be aware that the index of an associative array is not a numeric context.
If arraySalary had been declared with -a instead of -A, then this would have been fine:
arraySalary[currentIndex]=${biggerNumber}
because the index of an ordinaey array is a numeric context, and in numeric contexts you can use variabke names without $. But since it is associative, what that does is set the element whose key is the string currentIndex. What you want is:
arraySalary[$currentIndex]=${biggerNumber}
It is not necessary to write $((currentIndex)); bash does not distinguish between integers and strings which contain the integer.
Sorting an array just by its value will be easy. In the case that you sort keys
by its value by using an assoc array, you need to introduce another array to hold the order of the elements because an assoc array is orderless.
Let's name the new array index which holds (1 8 2 ..) then the script will look like:
#!/bin/bash
declare -A arraySalary=([1]=1000 [8]=3000 [2]=2000)
declare -a index=("${!arraySalary[#]}")
sortedDesc=false
while ! $sortedDesc ;
do
sortedDesc=true
for ((i=0; i<$((${#index[#]} - 1)); i++))
do
if [[ ${arraySalary[${index[$i]}]} -lt ${arraySalary[${index[$(($i + 1))]}]} ]]
then
sortedDesc=false
biggerIndex=${index[$(($i + 1))]}
index[$(($i + 1))]=${index[$i]}
index[$i]=$biggerIndex
echo "swapped"
fi
done
done
echo "Printing new values"
# Print new values
for key in "${index[#]}";
do
echo $key "->" ${arraySalary[$key]}
done
which yields
swapped
swapped
swapped
Printing new values
8 -> 3000
2 -> 2000
1 -> 1000

How do you compare the value of an array to a variable in bash script?

I'm practicing bash and honestly, it is pretty fun. However, I'm trying to write a program that compares an array's value to a variable and if they are the same then it should print the array's value with an asterisk to the left of it.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite="black"
for i in {0..4};do echo ${color[$i]};
if {"$favorite"=$color[i]}; then
echo"* $color[i]"
done
output should be *black
There's few incorrect statements in your code that prevent it from doing what you ask it to. The comparison in bash is done withing square brackets, leaving space around them. You correctly use the = for string comparison, but should enclose in " the string variable. Also, while you correctly address the element array in the echo statement, you don't do so inside the comparison, where it should read ${color[$i]} as well. Same error in the asterisk print. So, here a reworked code with the fixes, but read more below.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite=black
for i in {0..4};do
echo ${color[$i]};
if [ "$favorite" = "${color[$i]}" ]; then
echo "* ${color[$i]}"
fi
done
While that code works now, few things that probably I like and would suggest (open to more expert input of course by the SO community): always enclose strings in ", as it makes evident it is a string variable; when looping an array, no need to use index variables; enclose variables always within ${}.
So my version of the same code would be:
#!/bin/bash
color=("red" "blue" "black" "brown" "yellow")
favorite="black"
for item in ${color[#]}; do
echo ${item}
if [ "${item}" = "${favorite}" ]; then
echo "* $item"
fi
done
And a pointer to the great Advanced Bash-Scripting Guide here: http://tldp.org/LDP/abs/html/

Shell Programming: Access Element of List

It is my understanding that when writing a Unix shell program you can iterate through a string like a list with a for loop. Does this mean you can access elements of the string by their index as well?
For example:
foo="fruit vegetable bread"
How could I access the first word of this sentence? I've tried using brackets like the C-based languages to no avail, and solutions I've read online require regular expressions, which I would like to avoid for now.
Pass $foo as argument to a function. Than you can use $1, $2 and so on to access the corresponding word in the function.
function try {
echo $1
}
a="one two three"
try $a
EDIT: another better version is:
a="one two three"
b=( $a )
echo ${b[0]}
EDIT(2): have a look at this thread.
Using arrays is the best solution.
Here's a tricky way using indirect variables
get() { local idx=${!#}; echo "${!idx}"; }
foo="one two three"
get $foo 1 # one
get $foo 2 # two
get $foo 3 # three
Notes:
$# is the number of parameters given to the function (4 in all these cases)
${!#} is the value of the last parameter
${!idx} is the value of the idx'th parameter
You must not quote $foo so the shell can split the string into words.
With a bit of error checking:
get() {
local idx=${!#}
if (( $idx < 1 || $idx >= $# )); then
echo "index out of bounds" >&2
return 1
fi
echo "${!idx}"
}
Please don't actually use this function. Use an array.

Resources