Bash array saving - arrays

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

Related

How to modify 2d array in shell script

I have the following sample code for my shell script:
#!/bin/bash
x[1,1]=0
x[2,1]=1
echo "x[1,1]=${x[1,1]}"
echo "x[2,1]=${x[2,1]}"
for i in {1..2}; do
x[$i,1]=${i}
echo "loop$i x[$i,1]=${i}"
done
echo "x[1,1]=${x[1,1]}"
echo "x[2,1]=${x[2,1]}"
and I am expecting for x[1,1] to have the value of 1 and x[2,2] to have the value of 2.
But when I run the script the result is:
$ ./test3.sh
x[1,1]=1
x[2,1]=1
loop1 x[1,1]=1
loop2 x[2,1]=2
x[1,1]=2
x[2,1]=2
I expect x[1,1] to retain the value of 1 but it happens to be 2 now. Is there something wrong with my script?
Bash does not have 2-D arrays. The best you can do is emulate them with associative arrays.
Add the following line to the beginning of your script:
declare -A x
This makes x into an associative array. When that is done, the script produces the output that you expect:
$ bash script
x[1,1]=0
x[2,1]=1
loop1 x[1,1]=1
loop2 x[2,1]=2
x[1,1]=1
x[2,1]=2
Bash indexed arrays
Unless declare -A is used, a bash array is just an indexed array. Let's define y as an indexed array:
$ y=()
Now, let's assign two values:
$ y[2,3]=1
$ y[22,3]=2
Now, let's use declare -p to find out what the contents of the array really are:
$ declare -p y
declare -a y='([3]="2")'
As you can see, there is only y[3]. The reason is that the index in an indexed array is subject to arithmetic expansion and, when given a list of comma-separated values, arithmetic expansion returns just the last one.
In other words, as far as bash is concerned, assignments to y[2,3] and y[22,3] are both just assignments to y[3]. The second assignment overwrites the first.
We can see this directly if we echo the results of arithmetic expansion:
$ echo $((3))
3
$ echo $((2,3))
3
$ echo $((22,3))
3
When given a list of comma-separated values, arithmetic expansion returns the last one. This is true even if the comma-separated list is a long one:
$ echo $((1+2,3*4,5,6,7,8))
8
It is always the last value which is returned.
Bash associative arrays
Let's examine what happens with associative arrays. Let's define z as an associative array and assign some values to it:
$ declare -A z
$ z[1,2]=1
$ z[3,4]=2
$ z["Jim Bob"]=3
Now, let's see what was stored in z:
$ declare -p z
declare -A z='([3,4]="2" ["Jim Bob"]="3" [1,2]="1" )'
This seems to be what you need.

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.

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.

Bash: iteration through array names

I am trying to write code to break up a large array into many different small arrays. Eventually the array I would be passed is one of unknown size, this is just my test subject. I have gotten this far:
#!/bin/bash
num=(10 3 12 3 4 4)
inArray=${#num[#]}
numArrays=$(($inArray/2))
remain=$(($inArray%2))
echo $numArrays
echo $remain
nun=0
if test $remain -gt $nun; then
numArrays=$(($numArrays+1))
fi
array=(1 2)
j=0
for ((i=0;i<$numArrays;i++, j=j+2)); do
array=("${num[#]:$j:2}")
echo "The array says: ${array[#]}"
echo "The size? ${#array[#]}"
done
What I am really having a problem with is : I would like to make the variable 'array' be able to change names slightly every time, so each array is kept and has a unique name after the loop. I have tried making the name array_$i but that returns:
[Stephanie#~]$ ./tmp.sh
3
0
./tmp.sh: line 16: syntax error near unexpected token `"${num[#]:$j:2}"'
./tmp.sh: line 16: ` array_$i=("${num[#]:$j:2}")'
[Stephanie#RDT00069 ~]$ ./tmp.sh
3
0
./tmp.sh: line 16: syntax error near unexpected token `$i'
./tmp.sh: line 16: ` array($i)=("${num[#]:$j:2}")'
Does anyone have any advice?
Thanks
I don't think you can really avoid eval here, but you might be able to do it safely if you're careful. Here's my approach:
for name in "${!array_*}"; do # Get all names starting with array_
i="${name#array_*}" # Get the part after array_
if [[ $i != *[^0-9]* ]]; then # Check that it's a number.
printf '%s is not a valid subarray name\n' "$name"
else
# Create a variable named "statement" that contains code you want to eval.
printf -v statement 'cur_array=( "${%s[#]}" )' "$name"
eval "$statement"
# Do interesting things with $cur_array
fi
done
Before this, when you're just creating the array, you know what $name should be, so just use the printf -v part.
To make it even safer, you could save all the allowed array names in another array and check that $name is a member.
With simple variables, you can use the declare keyword to make indirect assignments:
v=foo
declare $v=5
echo $foo # Prints 5
This doesn't extend to arrays in the obvious (to me, anyway) sense:
i=2
# This produces a syntax error
declare -a array_$i=("${num[#]:$j:2}")
Instead, you can declare an empty array
declare -a array_$i
or assign items one at a time:
declare -a array_$i[0]=item1 array_$i[1]=item2
Here's an example of using a for-loop to copy, say, the 3rd
and 4th letters of a big array into a smaller one. We use
i as the dynamic part of the name of the smaller array, and
j as the index into that array.
letters=(a b c d e f)
i=1
j=0
for letter in "${letters[#]:2:2}"; do
# E.g., i=0 and j=1 would result in
# declare -a array_0[1]=c
declare -a array_$i[$j]=$letter
let j+=1
done
done
echo ${array_1[#]}; # c d
${foo[#]:x:y} gives us elements x, x+1, ..., x+y-1 from foo, and
You can wrap the whole thing inside another for-loop to accomplish the goal of splitting letters into 3 smaller arrays:
# We'll create array_0, array_1, and array_2
for i in 0 1 2; do
# Just like our subset above, but start at position i*2 instead of
# a constant.
for letter in "${letters[#]:$((i*2)):2}"; do
declare -a array_$i[$j]=$letter
done
done
Once you manage to populate your three arrays, how do you access them without eval? Bash has syntax for indirect access:
v=foo
foo=5
echo ${!v} # echoes 5!
The exclamation point says to use the word that follows as a variable whose value should be used as the name of the parameter to expand. Knowing that, you might think you could do the following, but you'd be wrong.
i=1
v=array_$i # array_1
echo ${!v[0]} # array_1[0] is c, so prints c, right? Wrong.
In the above, bash tries to find a variable called v[0] and expand it to get the name of a parameter to expand. We actually have to treat our array plus its index as a single name:
i=1
v=array_$i[0]
echo ${!v} # This does print c
This should work, but this is not a good solution, another language may be better bash does not support multi dimensional arrays
eval array_$i='('"${num[#]:$j:2}"')'
And then, for example
eval 'echo "${array_'$i'[0]}"'

Resources