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

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.

Related

Bash array only contains one element [duplicate]

This question already has answers here:
Reading output of a command into an array in Bash
(4 answers)
Closed 1 year ago.
I'm new to bash scripting and trying to learn.
I have a variable $temp, that contains what I get from an API call, which is a long list of SHA1 hashes. I want to take what is in $temp and create an array that I can loop through and compare each entry to a string I have defined. Somehow, I can not seem to get it to work. The array I create always only contains only one element. It is like the rest of the string in $temp is being ignored.
I have tried multiple approaches, and can't get anything working the way I want it to.
Here's the code snippet:
temp=$(curl --silent --request GET "https://api.pwnedpasswords.com/range/$shortendHash")
echo "Temp is: $temp"
declare -A myArr
read myArr <<<$temp
echo ${myArr[#]}
echo ${myArr[0]}
echo ${myArr[1]}
When I run this, I get a full list of hashes, when I echo $temp (line 2), something like this:
"Temp is: 002C3CADF9FC86069F09961B10E0EDEFC45:1
003BE35BD9466D19258E79C8665AFB072F6:1
0075B2ADF782F9677BDDF8CC5778900D986:8
00810352C02B145FF304FCBD5BEF4F7AA9F:1
013720F40B99D6BCF6F08BD7271523EBB49:1
01C894A55EBCB6048A745B37EC2D989F102:1
030B46D53696C8889910EE0F9EB42CEAE97:4
03126B56153F0564F4A6ED6E734604C67E8:2
043196099707FCB0C01F8C2111086A92A0B:1
0452CABBEF8438F358F0BD8089108D0910E:5
0490255B206A2C0377EBD080723BF72DDAE:2
05AD1141C41237E061460DB5CA5412C1A48:4
05C1D058E4439F8F005EFB32E7553E6AA5B:1
067AF515CC712AC4ACA012311979DBC7C9A:2 ... and so on.."
But the
echo ${myArr[#]}
echo ${myArr[0]}
echo ${myArr[1]}
returns (first value is ${myArr[#]}, second is ${myArr[0]}, and last one is an empty line, indicating that there is no value at array position 1):
002C3CADF9FC86069F09961B10E0EDEFC45:1
002C3CADF9FC86069F09961B10E0EDEFC45:1
I hope someone can help with this.
Thanks a lot in advance!
declare -A myArr # creates an associative array (hash)
declare -a myArr # creates an array
Neither is necessary here. read can create the array for you. See help declare and help read.
temp=$(curl --silent --request GET "https://api.pwnedpasswords.com/range/$shortendHash")
# -a: assign the words read to sequential indices of the array
read -a myArr <<<"$temp"
declare -p myArr
Output:
declare -a myArr=([0]="002C3CADF9FC86069F09961B10E0EDEFC45:1" [1]="003BE35BD9466D19258E79C8665AFB072F6:1" [2]="0075B2ADF782F9677BDDF8CC5778900D986:8" [3]="00810352C02B145FF304FCBD5BEF4F7AA9F:1" [4]="013720F40B99D6BCF6F08BD7271523EBB49:1" [5]="01C894A55EBCB6048A745B37EC2D989F102:1" [6]="030B46D53696C8889910EE0F9EB42CEAE97:4" [7]="03126B56153F0564F4A6ED6E734604C67E8:2" [8]="043196099707FCB0C01F8C2111086A92A0B:1" [9]="0452CABBEF8438F358F0BD8089108D0910E:5" [10]="0490255B206A2C0377EBD080723BF72DDAE:2" [11]="05AD1141C41237E061460DB5CA5412C1A48:4" [12]="05C1D058E4439F8F005EFB32E7553E6AA5B:1" [13]="067AF515CC712AC4ACA012311979DBC7C9A:2")
Assuming ...
$(curl --silent --request GET "https://api.pwnedpasswords.com/range/$shortendHash")
Returns ...
002C3CADF9FC86069F09961B10E0EDEFC45:1
003BE35BD9466D19258E79C8665AFB072F6:1
0075B2ADF782F9677BDDF8CC5778900D986:8
00810352C02B145FF304FCBD5BEF4F7AA9F:1
013720F40B99D6BCF6F08BD7271523EBB49:1
01C894A55EBCB6048A745B37EC2D989F102:1
030B46D53696C8889910EE0F9EB42CEAE97:4
03126B56153F0564F4A6ED6E734604C67E8:2
043196099707FCB0C01F8C2111086A92A0B:1
0452CABBEF8438F358F0BD8089108D0910E:5
0490255B206A2C0377EBD080723BF72DDAE:2
05AD1141C41237E061460DB5CA5412C1A48:4
05C1D058E4439F8F005EFB32E7553E6AA5B:1
067AF515CC712AC4ACA012311979DBC7C9A:2
Then assign this to an array by wrapping in a set of parens:
# unset current variable
$ unset myArr
# no need to issue a `declare`; the `myArr=( ... )` will automatically create `myArr` as a normal integer-indexed array
$ myArr=( $(curl --silent --request GET "https://api.pwnedpasswords.com/range/$shortendHash") )
# verify array contents:
$ typeset myArr
declare -a myarr=([0]="002C3CADF9FC86069F09961B10E0EDEFC45:1" [1]="003BE35BD9466D19258E79C8665AFB072F6:1" [2]="0075B2ADF782F9677BDDF8CC5778900D986:8" [3]="00810352C02B145FF304FCBD5BEF4F7AA9F:1" [4]="013720F40B99D6BCF6F08BD7271523EBB49:1" [5]="01C894A55EBCB6048A745B37EC2D989F102:1" [6]="030B46D53696C8889910EE0F9EB42CEAE97:4" [7]="03126B56153F0564F4A6ED6E734604C67E8:2" [8]="043196099707FCB0C01F8C2111086A92A0B:1" [9]="0452CABBEF8438F358F0BD8089108D0910E:5" [10]="0490255B206A2C0377EBD080723BF72DDAE:2" [11]="05AD1141C41237E061460DB5CA5412C1A48:4" [12]="05C1D058E4439F8F005EFB32E7553E6AA5B:1" [13]="067AF515CC712AC4ACA012311979DBC7C9A:2")
Assuming OP wants to populate the array from the variable $temp, and assuming the white space in $temp consists of linefeeds and not spaces:
$ mapfile -t myArr <<< "${temp}"
$ typeset -p myArr
declare -a x=([0]="002C3CADF9FC86069F09961B10E0EDEFC45:1" [1]="003BE35BD9466D19258E79C8665AFB072F6:1" [2]="0075B2ADF782F9677BDDF8CC5778900D986:8" [3]="00810352C02B145FF304FCBD5BEF4F7AA9F:1" [4]="013720F40B99D6BCF6F08BD7271523EBB49:1" [5]="01C894A55EBCB6048A745B37EC2D989F102:1" [6]="030B46D53696C8889910EE0F9EB42CEAE97:4" [7]="03126B56153F0564F4A6ED6E734604C67E8:2" [8]="043196099707FCB0C01F8C2111086A92A0B:1" [9]="0452CABBEF8438F358F0BD8089108D0910E:5" [10]="0490255B206A2C0377EBD080723BF72DDAE:2" [11]="05AD1141C41237E061460DB5CA5412C1A48:4" [12]="05C1D058E4439F8F005EFB32E7553E6AA5B:1" [13]="067AF515CC712AC4ACA012311979DBC7C9A:2")

How can I see which values exists in Bash array?

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

How to increment value in associative array in BASH

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

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