Get variable name while iterating over array in bash - linux

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?

Related

how can i make the lines variable in a file? [duplicate]

I'm trying to read from a file, that has multiple lines, each with 3 informations I want to assign to the variables and work with.
I figured out, how to simply display them each on the terminal, but can't figure out how to actually assign them to variables.
while read i
do
for j in $i
do
echo $j
done
done < ./test.txt
test.txt:
1 2 3
a b c
So I want to read the line in the outer loop, then assign the 3 variables and then work with them, before going to the next line.
I'm guessing I have to read the values of the lines without an inside loop, but I can't figure it out right now.
Hope someone can point me in the right direction.
I think all you're looking for is to read multiple variables per line: the read command can assign words to variables by itself.
while read -r first second third; do
do_stuff_with "$first"
do_stuff_with "$second"
do_stuff_with "$third"
done < ./test.txt
The below assumes that your desired result is the set of assignments a=1, b=2, and c=3, taking the values from the first line and the keys from the second.
The easy way to do this is to read your keys and values into two separate arrays. Then you can iterate only once, referring to the items at each position within those arrays.
#!/usr/bin/env bash
case $BASH_VERSION in
''|[123].*) echo "ERROR: This script requires bash 4.0 or newer" >&2; exit 1;;
esac
input_file=${1:-test.txt}
# create an associative array in which to store your variables read from a file
declare -A vars=( )
{
read -r -a vals # read first line into array "vals"
read -r -a keys # read second line into array "keys"
for idx in "${!keys[#]}"; do # iterate over array indexes (starting at 0)
key=${keys[$idx]} # extract key at that index
val=${vals[$idx]} # extract value at that index
vars[$key]=$val # assign the value to the key inside the associative array
done
} < "$input_file"
# print for debugging
declare -p vars >&2
echo "Value of variable a is ${vars[a]}"
See:
BashFAQ #6 - How can I use variable variables (indirect variables, pointers, references) or associative arrays?
The bash-hackers page on the read builtin, documenting use of -a to read words into an array.

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.

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 to use the value of a string in bash as a variable name

Hopefully I can make it clear. I would like to create a filename out of different strings in bash. For example hmd.sh so h, m, d are different values (number 0..9 or letter aA..zZ). So for example I want to convert
h=1 m=11 and d=12 to 1aA.sh. h=> 1, m=>a and d=>A
To declare variables like
a01=1; a02=2 .. a09=9, a10=0; a11=a; a12=b and so on. h(1)=a01=1 m(11)=a11=a
and
d(12)=a12=A.
To test it I wrote this:
#!/bin/bash
dd01="1"
aa="01"
bb="dd$aa"
echo $bb
But of course $bb is dd01 and not its value. How can I make $bb its value of 1?
Associative arrays make this kind of thing much more readable.
However your answer is "variable indirection"
$ echo $bb
dd01
$ echo ${!bb}
1
Do not listen to any advice suggesting eval -- you open yourself up to all kinds of code injection.
The only way to expand a variable inside another is in an array when the variable is enclosed in the key's brackets like [$var].
You could store your values in an associative array, and reference them like so:
declare -A arr
arr[dd01]="1"
arr[aa]="01"
arr[bb]="dd${arr[aa]}"
echo ${arr[${arr[bb]}]}
Using arrays like this may be more convoluted for this example than referencing the variable name using ${!bb} syntax, but if you need to do this while keeping different sets of variables that may need to reference each other, creating an associative array may make more organizational sense.
I rewrote your code example as follows which gives you the value of 1 which is what you are trying to achieve.
#!/bin/bash
dd01="1"
aa="01"
bb="dd$aa"
echo $[$bb]
This did the trick:
#!/bin/bash
dd=1234567890aAbBcC
aa="11"
echo ${dd:(aa-1):1}
Appearantly 1 is the 0 position and aa can also be like 01 and still work!
Thank for all the advices.
I found my answer here:
http://tldp.org/LDP/abs/html/string-manipulation.html
This should do the job:
#!/bin/bash
dd01="1"
aa="01"
bb="dd$aa"
eval echo \$$bb
You can use the '$' operator to access the value of a variable. For example...
d = 'Hi'
e = ' there '
f = 'friend.'
foo = '$d$e$f'
This would cause the value of foo to be 'Hi there friend.'
Hope this helps.
Use eval:
#!/bin/bash
dd01="1"
aa="01"
eval bb=\$dd$aa
echo $bb
This script outputs the expected 1.

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

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

Resources