Bash - Print value of variable in array - arrays

I want to do something like this
A='123'
B='143'
C='999'
declare -a arr=(A B C)
for i in "{$arr[#]}"
do
echo "#i" "$i"
done
Which should give me the output of
A 123
B 143
C 999
But instead I receive the variable names, not the value in the output (I just see "A #i" in the output...

If you want to store the variable names in the loop, rather than copy their values, then you can use the following:
for i in "${arr[#]}"; do
echo "${!i}"
done
This means that the value of i is taken as a name of a variable, so you end up echoing $A, $B and $C in the loop.
Of course, this means that you can print the variable name at the same time, e.g. by using:
echo "$i: ${!i}"
It's not exactly the same, but you may also be interested in using an associative array:
declare -A assoc_arr=( [A]='123' [B]='143' [C]='999' )
for key in "${!assoc_arr[#]}"; do
echo "$key: ${assoc_arr[$key]}"
done

I suggest to add $:
declare -a arr=("$A" "$B" "$C")

Related

How to check if array values are empty, in bulk?

I want a script bash/sh/ksh which compare a lot of variables, maybe in an array, and tell me if variable is empty or not.
I think something like this, it but doesn't work.
ARRAY=(
bash="yes"
cash=""
trash="no"
empty=""
)
for var in "${ARRAY[#]}"; do
if [ "$var" == "$empty" ]
then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
I want an output like this
bash is not empty
cash is empty...
If you're willing to limit your runtime environment to a recent version of bash (or modify the code to support ksh93's equivalent syntax),
#!/bin/bash
# ^^^^ -- specifically, bash 4.0 or newer
declare -A array # associative arrays need to be declared!
array=( [bash]="yes" [cash]="" [trash]="no" [empty]="" )
for idx in "${!array[#]}"; do
if [[ ${array[$idx]} ]]; then
echo "$idx is not empty"
else
echo "$idx is empty"
fi
done
To iterate over keys in an array, as opposed to values, the syntax is "${!array[#]}", as opposed to "${array[#]}"; if you merely iterate over the values, you don't know the name of the one currently being evaluated.
Alternately, let's say we aren't going to use an array at all; another way to set a namespace for variables you intend to be able to treat in a similar manner is by prefixing them:
#!/bin/bash
val_bash=yes
val_cash=
val_trash=no
val_empty=
for var in "${!val_#}"; do
if [[ ${!var} ]]; then
echo "${var#val_} is not empty"
else
echo "${var#val_} is empty"
fi
done
This works (on bash 3.x as well) because "${!prefix#}" expands to the list of variable names starting with prefix, and "${!varname}" expands to the contents of a variable whose name is itself stored in the variable varname.
Iterate over the array elements, and inside the loop for read set IFS as = to get variable and it's value in two separate variables, then check if the value is empty:
for i in "${array[#]}"; do
IFS== read var value <<<"$i"
if [ -z "$value" ]; then
echo "$var is empty"
else
echo "$var is not empty"
fi
done
Outputs:
bash is not empty
cash is empty
trash is not empty
empty is empty

Variable in bash array name

I have this code:
#!/bin/sh
...
for cnumber in `seq $CNUMBER`; do
declare -a CAT$cnumber
let i=0
while IFS=$'\n' read -r line_data; do
CAT$cnumber[i]="${line_data}"
((++i))
done < input_file_$cnumber
done
It works if I use array name without variable $cnumber.
But I want to create multiple arrays (CAT0, CAT1, CAT2 etc.) and to read lines:
from file 'input_file_0' to array 'CAT0'
from file 'input_file_1' to array 'CAT1'
from file 'input_file_2' to array 'CAT2'
etc.
What syntax to use $cnumber variable in array name (CAT1) and in input file_name?
for cnumber in `seq $CNUMBER`; do
declare -a CAT$cnumber
let i=0
while IFS=$'\n' read -r line_data; do
eval CAT$cnumber[i]='"${line_data}"'
((++i))
done < input_file_$cnumber
done
Mainly, that adds the word "eval" which makes bash evaluate the rest of the line. Before that, bash expands variables, thus CAT$number will be something like CAT1 when the line is evaluated. Keep in mind that "${line_date}" would be subject to variable expansion before eval evaluates the line if it would not be protected by single quotes. That might have unexpected effects if the $line_data would contain blank spaces. See this simplified example:
a=b
l="hello date"
eval $a="$l" # executes "date", has no other effect
echo $b # prints an empty line
eval $a='"$l"' # sets b to "hello date"
echo $b # prints that: hello date
In reply to the comment of Etan Reisner below, I add another solution that avoids "eval" and instead uses references, which are available in bash version 4.3 or higher. In that case, using references is preferable for the reason Etan pointed out, and also, for my opinion, because it is more natural:
for cnumber in `seq $CNUMBER`; do
declare -a CAT$cnumber # be sure the array is declared before ...
declare -n ref=CAT$cnumber # ... you declare ref to reference the array
let i=0
while IFS=$'\n' read -r line_data; do
ref[$i]="${line_data}"
((++i))
done < input_file_$cnumber
done
First, your shebang needs to be
#!/bin/bash
since declare is a bash built-in.
Next, use declare to define the array elements
for cnumber in `seq $CNUMBER`; do
declare -a CAT$cnumber
let i=0
while IFS=$'\n' read -r line_data; do
declare "CAT$cnumber[$i]=${line_data}"
((++i))
done < input_file_$cnumber
done

Bash array saving

The think is that I want to save something to an array in bash. The point is that I want one name of file for one array. So I don't know how much arrays I will have.
#!/bin/bash
declare -A NAMES
index=0
for a in recursive.o timeout.o print_recursive.o recfun.o
do
NAMES[$index]=$a
index=$((index+1))
echo ${NAMES[ $index ]}
done
When I run script with -x I can see that NAMES[$index], the index is not there represented as number so the whole thing doesn't work.
The error is at lines 7 and 8. Swap them and it will work.
When index have value 0 you set NAMES[0]=recursive.o, then increment index and print NAMES[1] which not set. And same thing for another elements. Because that there is no output.
Your loop should looks like this:
for a in recursive.o timeout.o print_recursive.o recfun.o
do
NAMES[$index]=$a
echo ${NAMES[$index]}
index=$((index+1))
done
The problem is in the following:
declare -A NAMES
This makes an associative array NAMES. Quoting from help declare:
Options which set attributes:
-a to make NAMEs indexed arrays (if supported)
-A to make NAMEs associative arrays (if supported)
You needed to say:
declare -a NAMES
May be you are trying to do this:
#!/bin/bash
declare -a NAMES
for a in recursive.o timeout.o print_recursive.o recfun.o; do
NAMES+=( "$a" )
done
for (( x=0; x<${#NAMES[#]}; x++ )); do
echo "Index:$x has Value:${NAMES[x]}"
done
Output:
Index:0 has Value:recursive.o
Index:1 has Value:timeout.o
Index:2 has Value:print_recursive.o
Index:3 has Value:recfun.o
Accessing the index which is not set is throwing it off.
NAMES[$index]=$a #Setting up an array with index 0
index=$((index+1)) #Incrementing the index to 1
echo ${NAMES[ $index ]} #Accessing value of index 1 which is not yet set

foreach loop through associative array in bash only returns last element

This should print the whole associative array to the console:
#!/bin/sh
declare -a array=([key1]='value1' [key2]='value2')
for key in ${!array[#]}; do
echo "Key = $key"
echo "Value = ${array[$key]}"
done
echo ${array[key1]}
echo ${array[key2]}
Instead it prints oly the last variable:
[mles#sagnix etl-i_test]$ ./test.sh
Key = 0
Value = value2
value2
value2
Where is my fault?
#htor:
Bash Version is 3.2.25(1)-release.
Associative arrays are supported in Bash 4 and newer versions. An array declared with the -a option is just a regular array that can be indexed by integers, not keys. This declaration results in the array with one element value2. When iterating over the keys with for key in ${!array[#]} the value of $key is 0 and therefore you get the first element.
Given the error output you get when trying to use -A to declare to array, I assume your Bash version is older than 4. Inspect the variable $BASH_VERSION.
For a deeper explaination of arrays, see http://mywiki.wooledge.org/BashGuide/Arrays.
#!/bin/bash
declare -A array=([key1]='value1' [key2]='value2')
for key in ${!array[#]}; do
echo "array[$key] = ${array[$key]}"
done
echo ${array[key1]}
echo ${array[key2]}

Copying a Bash array fails

Assigning arrays to variables in Bash script seems rather complicated:
a=("a" "b" "c")
b=$a
echo ${a[0]}
echo ${a[1]}
echo ${b[0]}
echo ${b[1]}
leads to
a
b
a
instead of
a
b
a
b
Why? How can I fix it?
If you want to copy a variable that holds an array to another name, you do it like this:
a=('a' 'b' 'c')
b=( "${a[#]}" )
Why?
If a is an array, $a expands to the first element in the array. That is why b in your example only has one value. In bash, variables that refer to arrays aren't assignable like pointers would be in C++ or Java. Instead variables expand (as in Parameter Expansion) into strings and those strings are copied and associated with the variable being assigned.
How can I fix it?
To copy a sparse array that contains values with spaces, the array must be copied one element at a time by the indices - which can be obtained with ${!a[#]}.
declare -a b=()
for i in ${!a[#]}; do
b[$i]="${a[$i]}"
done
From the bash man page:
It is possible to obtain the keys (indices) of an array as well as the values.
${!name[#]} and ${!name[*]} expand to the indices assigned in array variable name.
The treatment when in double quotes is similar to the expansion of the special
parameters # and * within double quotes.
Here's a script you can test on your own:
#!/bin/bash
declare -a a=();
a[1]='red hat'
a[3]='fedora core'
declare -a b=();
# Copy method that works for sparse arrays with spaces in the values.
for i in ${!a[#]}; do
b[$i]="${a[$i]}"
done
# does not work, but as LeVar Burton says ...
#b=("${a[#]}")
echo a indicies: ${!a[#]}
echo b indicies: ${!b[#]}
echo "values in b:"
for u in "${b[#]}"; do
echo $u
done
Prints:
a indicies: 1 3
b indicies: 1 3 # or 0 1 with line uncommented
values in b:
red hat
fedora core
This also works for associative arrays in bash 4, if you use declare -A (with capital A instead of lower case) when declaring the arrays.

Resources