How can I see which values exists in Bash array? - arrays

Given an array (in Bash), is there a command that prints the contents of the array according to indices?
Something like that: arr[0]=... , arr[1]=... ,...
I know that I can print it in a for loop, but I am looking for a command that does it.

Given an array with contiguous indices starting at 0:
$ arr=(one two three)
And non-contiguous indices:
$ declare -a arr2='([0]="one" [2]="two" [5]="three")'
You can print the values:
$ echo ${arr[*]} # same with arr2
one two three
Or, use a C style loop for arr:
$ for (( i=0;i<${#arr[#]};i++ )); do echo "arr[$i]=${arr[$i]}"; done
arr[0]=one
arr[1]=two
arr[2]=three
But that won't work for arr2.
So, you can expand the indices (contiguous or not) and print index, value like so:
$ for i in "${!arr2[#]}"; do echo "arr2[$i]=${arr2[$i]}"; done
arr2[0]=one
arr2[2]=two
arr2[5]=three
Or inspect it with declare -p:
$ declare -p arr
declare -a arr='([0]="one" [1]="two" [2]="three")'
Which also works if the array has non-contiguous indices (where the C loop would break):
$ declare -p arr2
declare -a arr2='([0]="one" [2]="two" [5]="three")'
Note: A common mistake is to use the sigil $ and thinking you are addressing that array or named value. It is the unadorned name that is used since the sigil will dereference and tell details of the name contained instead of that name:
$ k=arr
$ declare -p $k
declare -a arr='([0]="one" [1]="two" [2]="three")' # note this is 'arr' , not 'k'
$ declare -p k
declare -- k="arr"
Since declare with no arguments will print the entire Bash environment at that moment, you can also use utilities such as sed grep or awk against that output:
$ declare | grep 'arr'
arr=([0]="one" [1]="two" [2]="three")
k=arr

Related

declare inside a bash funcion not working as expected with associative arrays

I declare an associative array:
$ declare -A dikv
Initialize it with some key/value pairs:
$ dikv=( ["k1"]="v1" ["k2"]="v2" ["k3"]="v3" ["k4"]="v4")
Then I can save the contents to a file:
$ declare -p dikv > /tmp/dikv.saved
This is the content of /tmp/dikv.saved:
$ cat /tmp/dikv.saved
declare -A dikv=([k4]="v4" [k1]="v1" [k2]="v2" [k3]="v3" )
Now, in a new shell environment, I can load the saved associative array:
$ source /tmp/dikv.saved
And the content is properly accesible:
$ echo "${!dikv[#]}"
k4 k1 k2 k3
$ echo "${dikv[#]}"
v4 v1 v2 v3
This works as expected, nice.
Now I want to do exactly the same but using a bash function:
#! /bin/bash
declare -A dikv
backup_dictionary()
{
local -n dict_ref=$1
FILE=$2
echo "${!dict_ref[#]}"
echo "${dict_ref[#]}"
declare -p dict_ref > $FILE
}
dikv=( ["k1"]="v1" ["k2"]="v2" ["k3"]="v3" ["k4"]="v4")
backup_dictionary dikv /tmp/dikv.saved
As you can see, I pass the associative array to the function using local -n. When I run this code, the echo's inside the function print the content of the associative array properly. So, as far as I understand, the associative array has been properly passed as argument.
However, this statement is not working as expected:
$ declare -p dict_ref > $FILE
This is the content of $FILE:
$ cat /tmp/dikv.saved
declare -n dict_ref="dikv"
I expected to see something like this:
dikv=( ["k1"]="v1" ["k2"]="v2" ["k3"]="v3" ["k4"]="v4")
As it happens when not using a bash funcion. Can you explain what happens here? And what should be the proper way to fix this? Thanks!
Your dict_ref stores the string dikv. Hence if you do a declare -p dict_ref, I would expect to see as output something like
declare -n dict_ref=dikv
Note that dereferencing (due to the -n declaration) occurs during parameter expansion, i.e. when you do a $dict_ref.
You could do a
declare -p "$1" >$FILE

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.

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.

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.

Remove the last element from an array

I want to remove the last entry in my array, and I want the array to show me that it has 1 less entry when I am using the ${#array[#]}. This is the current line I am using:
unset GreppedURLs[${#GreppedURLs[#]} -1]
Please correct me and show me the right way.
The answer you have is (nearly) correct for non-sparse indexed arrays¹:
unset 'arr[${#arr[#]}-1]'
Bash 4.3 or higher added this new syntax to do the same:
unset arr[-1]
(Note the single quotes: they prevent pathname expansion).
Demo:
arr=( a b c )
echo ${#arr[#]}
3
for a in "${arr[#]}"; do echo "$a"; done
a
b
c
unset 'arr[${#arr[#]}-1]'
for a in "${arr[#]}"; do echo "$a"; done
a
b
Punchline
echo ${#arr[#]}
2
(GNU bash, version 4.2.8(1)-release (x86_64-pc-linux-gnu))
¹ #Wil provided an excellent answer that works for all kinds of arrays
You must remove the blank before -1.
If you'd like an answer which won't eat your kittens, try this:
array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6}")'
index=("${!array[#]}");
# declare -a index='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="10")'
unset 'array[${index[#]: -1}]';
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
And there you have it - removing the last element. Now I'll present a much easier answer which probably meets your needs but has a caveat:
array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6}")'
array=("${array[#]::${#array[#]}-1}");
# declare -a array='([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")'
This version takes a shortcut. It re-indexes the array and drops the last element. Unfortunately you can also see that the index has not been maintained. The values and their order has been. If you don't care about the index then this is probably the answer you wanted.
Both of the above answers will also work on bash 4 Associative Arrays.
--
The chosen answer is not safe. Here's an example:
array=([1]=1 {2..5} [10]=6);
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [10]="6")'
unset 'arr[${#arr[#]}-1]';
# declare -a array='([1]="1" [2]="2" [3]="3" [4]="4" [10]="6")'
Okay so as you can see it is unsetting the element with index 5 because it incorrectly calculated the index of the last element of the array. It failed because it operated on an assumption that all arrays are zero-based, and not sparse. This answer will fail on arrays starting with anything other than zero, arrays which are sparse, and obviously must fail for an associative array with 'fubar' for the last element.
For any indexed array (sparse or not), since bash 4.3+ (and ksh93+), this is the simplest of solutions:
unset 'array[-1]'
The quotes are needed to avoid shell expansion in bash if the -1 is an arithmetic expression or a variable. This also works correctly:
a=3; unset 'arr[ a - 4 * 1 ]'
But will not work if unquoted ('') as the * will be expanded to the list of files in the present working directory ($pwd).
For older bash versions: this works since bash 3.0 for non-sparse arrays:
unset 'arr[ ${#arr[#]}-1 ]'
Example:
$ arr=( {a..i} ); declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [7]="h")
$ unset 'arr[ ${#arr[#]}-1 ]'; declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g")
This will not work for sparse arrays (with some holes):
$ arr=( {a..g} [9]=i ); declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [9]="i")
$ unset 'arr[ ${#arr[#]}-1 ]'; declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g" [9]="i")
This happens because the count of elements (${#arr[#]}) is 8 and 8-1 is 7.
So, the command will unset arr[7], which doesn't exist. Nothing is done.
A solution, that also work for Associative arrays (in whatever it could mean "the last element" in an unsorted list) is to generate a new array of indexes.
Then use the last index to unset that element.
Assuming arr is already defined (for bash 3.0+):
$ index=( "${!arr[#]}" ) # makes index non-sparse.
$ unset 'arr[${index[#]}-1]' # unset the last index.
$ declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e" [5]="f" [6]="g")
A slightly more portable (works in ksh93), that looks ugly, solution is:
$ arr=( {a..e} [9]=i )
$ index=( "${!arr[#]}" )
$ unset "arr[ ${index[${#index[#]}-1]} ]" # Yes, double quotes.
$ declare -p arr
declare -a arr=([0]="a" [1]="b" [2]="c" [3]="d" [4]="e")
Or (again, double quotes for ksh):
$ unset "arr[${index[#]: -1}]"
If you want to avoid the space and the negative number, make it a variable:
$ a="-1"; unset "arr[${index[#]:a}]"
The following works fine for Mac/bash#3.x and Linux (ubuntu/bash#4.x)
unset arr[$[${#arr[#]}-1]] # non-sparse array only
in more details:
len=${#arr[#]}
idx=$[$len-1] # <=> $(($len-1))
unset arr[$idx]
In your function, you could add the following:
target="${#:$(($#)):1}"
set -- "${#:1:$(($#-1))}"

Resources