How to increment value in associative array in BASH - arrays

I have array:
declare -A server
server[172.16.170.1]='t1.com'
server[172.16.170.2]='t2.com'
server[172.16.170.3]='t3.com'
server[172.16.170.4]='t4.com'
server[172.16.170.5]='t5.com'
.....
I don't want to write every time, "t1,com,t2.com ..."
I want to increment it
Ok:
first=0
first=$(($first+1))
It is work for the first element.
first=0
first=$(($first+1))
declare -A server
server[172.16.170.1]='t$first.com'
server[172.16.170.2]='t$first.com'
server[172.16.170.3]='t$first.com'
server[172.16.170.4]='t$first.com'
server[172.16.170.5]='t$first.com'
.....
In output we will have:
server[172.16.170.1]=t1.com
server[172.16.170.2]=t1.com
server[172.16.170.3]=t1.com
server[172.16.170.4]=t1.com
server[172.16.170.5]=t1.com
.....
I know, that we should use loop, but if i have a lot of servers, how i should use loop "for" ? With all my array variables ?

Couple of things missed out, use an arithmetic operator in bash as $((..)) with the pre-increment operator! under double-quotes
first=0
declare -A server
server[172.16.170.1]="t$((++first)).com"
server[172.16.170.2]="t$((++first)).com"
server[172.16.170.3]="t$((++first)).com"
server[172.16.170.4]="t$((++first)).com"
server[172.16.170.5]="t$((++first)).com"
and for printing the associative array just use the declare built-in.
declare -p server
declare -A server='([172.16.170.1]="t1.com" [172.16.170.3]="t3.com" [172.16.170.2]="t2.com" [172.16.170.5]="t5.com" [172.16.170.4]="t4.com" )'
And the for-loop version of the same. This will work ONLY in associative arrays (with declare -A array)
count=0
for i in "${!server[#]}"; do
server["${i}"]="t$((++count)).com"
done

How about this:
declare -A server
for ((i=1; i<=5; i++))
do
server[172.16.170.$i]=t$i.com
done

Related

Is the -a flag necessary to create a bash array?

What is the added value of declaring a bash array with the -a flag like below:
with flag
declare -a arr1
arr1[0]="array1-zero"
arr1[1]="array1-one"
echo ${arr1[0]}
echo ${arr1[1]}
...when the behaviour is the same if you omit the option, like below:
without -a option
declare arr2
arr2[0]="array2-zero"
arr2[1]="array2-one"
echo ${arr2[0]}
echo ${arr2[1]}
The same goes for associative arrays (option -A).
Omitting it renders the same result as providing it.
You can skip declare -a for numerically-indexed arrays, but you can't skip declare -A for associative arrays. To demonstrate the latter:
declare array
array[one]=1
array[two]=2
echo "${array[one]}"
...emits:
2
Why? Let's use declare -p array to see:
$ declare -p array
declare -a array='([0]="2")'
The words one and two were both coerced to the numeric value 0, so they overwrote the same key.
That said, declare -a array isn't a complete noop. Compare:
unset array # start from a blank slate
declare -a array
declare -p array
...emits:
declare -a array='()'
Whereas if we leave out the -a, we see that no type data is assigned, so our variable is assumed to just be a string until it's assigned to in a way that makes it clear that it's supposed to be an array:
unset array # start from a blank slate
declare array
declare -p array
...emits declare -- array="", unless there's as assignment:
unset array # start from a blank slate
declare array
array[0]="zero"
declare -p array
...which properly emits declare -a array='([0]="zero")', meaning that in the above code, only after the array[0]="zero" line does array actually become an array.

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 element becoming the name of a new array

I've got an array bucket1=('10' '22' 'bucket1')
As you can see, one of the elements is the name of the array bucket1
Now I'm creating a new array by copying bucket1:
array1=("${bucket1[#]}")
Now I'm changing one of the elements in array1:
array1[1]='30'
echo ${array1[#]} gives 10 30 bucket1
Now I want to feed that change back to the array bucket1, but without knowing that array1 was created from bucket1. Instead I want to use the third element of array1, namely bucket1.
Something like:
declare -a ${array1[2]}=${array1[#]}
So that I end up with new bucket1 array, containing ('10' '30' 'bucket1')
In short:
I want to copy an array, alter the copied array, apply the changes from the copied array in the original array using one of the elements from the copied array as the name of the original array.
It this possible?
bucket1=(10 20 bucket1)
tmp=("${bucket1[#]}")
tmp[1]=30
declare -a "${tmp[2]}"=("${tmp[#]}")
bash: syntax error near unexpected token `('
Hmm that doesn't work. Try one-by-one
for i in ${!tmp[#]}; do declare "${tmp[2]}[$i]"="${tmp[i]}"; done
echo ${bucket1[1]}
30
This is MUCH easier in ksh93
$ bucket1=(10 20 bucket1)
$ nameref tmp=bucket1
$ tmp[1]=30
$ echo ${bucket1[1]}
30
You can use read -ra instead of declare here:
$> bucket1=('10' '22' 'bucket1')
$> array1=("${bucket1[#]}")
$> array1[1]='30 50'
$> declare -p array1
declare -a array1='([0]="10" [1]="30 50" [2]="bucket1")'
$> IFS=$'^G' && read -ra "${array1[2]}" < <(printf "%s^G" "${array1[#]}")
$> declare -p "${array1[2]}"
declare -a bucket1='([0]="10" [1]="30 50" [2]="bucket1")'
$> declare -p bucket1
declare -a bucket1='([0]="10" [1]="30 50" [2]="bucket1")'
All these declare -p have been used to print the array contents and can be removed in real script.
^G is typed using ControlVG together.
With a little work, you can get the value of the array in a form suitable for use in the argument to declare.
IFS="=" read _ value <<< "$(set | grep '^array1=')"
declare -a "${array1[2]}=$value"
The quotes around the command substitution are necessary to work around a bug that is fixed in bash 4.3. However, if you have that version of bash, you can use named references to simplify this:
declare -n tmp=${array1[2]}
tmp=("${array1[#]}")
Try this:
unset ${array1[2]}
declare -a ${array1[2]}="`echo ${array1[#]}`"
First we clear the array and then the output of echo will be stored in the new array name.

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

How to copy an array in Bash?

I have an array of applications, initialized like this:
depends=$(cat ~/Depends.txt)
When I try to parse the list and copy it to a new array using,
for i in "${depends[#]}"; do
if [ $i #isn't installed ]; then
newDepends+=("$i")
fi
done
What happens is that only the first element of depends winds up on newDepends.
for i in "${newDepends[#]}"; do
echo $i
done
^^ This would output just one thing. So I'm trying to figure out why my for loop is is only moving the first element. The whole list is originally on depends, so it's not that, but I'm all out of ideas.
a=(foo bar "foo 1" "bar two") #create an array
b=("${a[#]}") #copy the array in another one
for value in "${b[#]}" ; do #print the new array
echo "$value"
done
The simplest way to copy a non-associative array in bash is to:
arrayClone=("${oldArray[#]}")
or to add elements to a preexistent array:
someArray+=("${oldArray[#]}")
Newlines/spaces/IFS in the elements will be preserved.
For copying associative arrays, Isaac's solutions work great.
The solutions given in the other answers won't work for associative arrays, or for arrays with non-contiguous indices. Here are is a more general solution:
declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
temp=$(declare -p arr)
eval "${temp/arr=/newarr=}"
diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output
And another:
declare -A arr=([this]=hello [\'that\']=world [theother]='and "goodbye"!')
declare -A newarr
for idx in "${!arr[#]}"; do
newarr[$idx]=${arr[$idx]}
done
diff <(echo "$temp") <(declare -p newarr | sed 's/newarr=/arr=/')
# no output
Try this: arrayClone=("${oldArray[#]}")
This works easily.
array_copy() {
set -- "$(declare -p $1)" "$2"
eval "$2=${1#*=}"
}
# Usage examples:
these=(apple banana catalog dormant eagle fruit goose hat icicle)
array_copy these those
declare -p those
declare -A src dest
source=(["It's a 15\" spike"]="and it's 1\" thick" [foo]=bar [baz]=qux)
array_copy src dest
declare -p dest
Note: when copying associative arrays, the destination must already exist as an associative array. If not, array_copy() will create it as a standard array and try to interpret the key names from the associative source as arithmetic variable names, with ugly results.
Isaac Schwabacher's solution is more robust in this regard, but it can't be tidily wrapped up in a function because its eval step evaluates an entire declare statement and bash treats those as equivalent to local when they're inside a function. This could be worked around by wedging the -g option into the evaluated declare but that might give the destination array more scope than it's supposed to have. Better, I think, to have array_copy() perform only the actual copy into an explicitly scoped destination.
You can copy an array by inserting the elements of the first array into the copy by specifying the index:
#!/bin/bash
array=( One Two Three Go! );
array_copy( );
let j=0;
for (( i=0; i<${#array[#]}; i++)
do
if [[ $i -ne 1 ]]; then # change the test here to your 'isn't installed' test
array_copy[$j]="${array[$i]}
let i+=1;
fi
done
for k in "${array_copy[#]}"; do
echo $k
done
The output of this would be:
One
Three
Go!
A useful document on bash arrays is on TLDP.
Problem is to copy array in function to be visible in parent code. This solution works for indexed arrays and if before copying are predefined as declare -A ARRAY, works also for associative arrays.
function array_copy
# $1 original array name
# $2 new array name with the same content
{
local INDEX
eval "
for INDEX in \"\${!$1[#]}\"
do
$2[\"\$INDEX\"]=\"\${$1[\$INDEX]}\"
done
"
}
Starting with Bash 4.3, you can do this
$ alpha=(bravo charlie 'delta 3' '' foxtrot)
$ declare -n golf=alpha
$ echo "${golf[2]}"
delta 3
Managed to copy an array into another.
firstArray=()
secondArray=()
firstArray+=("Element1")
firstArray+=("Element2")
secondArray+=("${firstArray[#]}")
for element in "${secondArray[#]}"; do
echo "${element}"
done
I've found that this works for me (mostly :)) ...
eval $(declare -p base | sed "s,base,target,")
extending the sed command to edit any switches as necessary e.g. if the new structure has to be writeable, to edit out read-only (-r).
I've discovered what was wrong.. My if isn't installed test is two for loops that remove excess characters from file names, and spits them out if they exist on a certain web server. What it wasn't doing was removing a trailing hyphen. So, when it tested it online for availability, they were parsed out. Because "file" exists, but "file-" doesn't.

Resources