bash function returns with changed indexes - arrays

I have following code:
#!/bin/bash
function getSlots {
declare -A slots
for index in `seq 7 10`;
do
slots[$index]=$index
done
echo ${slots[#]}
}
slots=($(getSlots))
for i in ${!slots[#]};
do
echo "$i ${slots[$i]}"
done
When I run, my output is this:
0 10
1 7
2 8
3 9
Why do indexes change when I call the function?

arr=(...) re-indexes the array.
With your current approach, you cannot preserve the indexes as any information about them is lost as soon as you leave the function (you just echo the values).
You can use nameref (requires bash 4.3 or later) to modify the supplied array directly and since you are using only numbers as indexes, regular array will suffice:
#!/usr/bin/env bash
function get_slots {
local index # makes index local
local -n _arr=$1 # _arr points to the supplied array
_arr=() # empties the array
for index in {7..10}; do
_arr[index]=$index # assigns new values to the array
done
}
get_slots slots
for i in "${!slots[#]}"; do
echo "$i ${slots[$i]}"
done

Because the echo ${slots[#]} at the end of the function getSlots expands to echo 10 7 8 9 and that output is what you are assigning to the array slots by doing:
slots=($(getSlots))
Another interesting question would be why echo ${slots[#]} expands to echo 10 7 8 9 and not to echo 7 8 9 10.
That is because the slots inside getSlots is declared as an associative array, not an array, i.e.:
declare -A slots
Replace the line above with:
declare -a slots
and you will get the following output:
0 7
1 8
2 9
3 10

Related

Populate array in a for loop

I have an array of strings to pass through a script. The script is well-behaved, and will return error code 0 if the string "passes" and non-zero if it "fails." If the string passes, it should be included in a final array to be output or written to file or etc.
The problem I'm having is that the only item ending up in my final array is the first "passing" string.
#!/bin/bash
# validator.sh
if [[ $1 -le 10 ]]; then
exit 0
else
exit 1
fi
#!/bin/bash
# main.sh
numbers=(2 4 6 8 10 12 14 16)
keep=()
for n in ${numbers[#]}; do
if ./validator.sh $n; then
keep+=("$n")
fi
done
echo $keep
Running main.sh produces:
$ ./main.sh
2
but I expect 2 4 6 8 10
Unless you meant keep to be an array of matching elements, change:
keep+=("$n")
to
keep="$keep $n"
That would work with any Bourne compatible shell and is therefore better, if you're looking for BASH specific solution, the below will also work:
keep+="${n} "
If you DO want it to be an array, then in order to output all elements, you can use:
echo ${keep[#]}
As noted by #Jetchisel and #kamilCuk in the comments.
Since you wrote you want to output all elements or save them to a file, I had assumed you don't actually need an array here but perhaps you plan to use this data in other ways later:)

Odd results when parsing array in bash

I'm building a bash script that gets information from GeekTool and parses the results. The so far the script looks like this:
1 #!/bin/bash
2
3 clear
4
5 geeklets=`osascript <<TEXT
6 Tell Application "GeekTool Helper"
7 geeklets
8 End Tell
9 TEXT`
10
11 IFS="," read -a geekletArray <<< "$geeklets"
12
13 echo "First Element in Array:"
14 echo ${geekletArray[0]}
15
16 echo ""
17
18 echo "All Array Elements:"
19 for element in ${geekletArray[#]}
20 do
21 echo $element
22 done
At line 14 I echo out the first element of the array which returns:
shell geeklet id 49610161-0A3C-49C6-9626-694370DE3101
but on the first iteration of the loop that steps through the array (line 21), the first element array is returned like this:
shell
geeklet
id
49610161-0A3C-49C6-9626-694370DE3101
In the loop the elements are returned in a newline delimited list.
Why is there a difference? Also, if I wanted to grab just the id value of the array (e.g. 49610161-0A3C-49C6-9626-694370DE3101), how would I go about it?
Thanks!
for element in "${geekletArray[#]}"
do
echo "$element"
done
Judicious use of quotes will protect you from undesired word splitting.
Quoting of arrays can be confusing. You can sidestep the issue completely by iterating over the array indices instead.
There are lots of ways to pull out the id value from the resulting string. I am assuming it will always be the last part of a space-delimited string. As such, I am using a bash parameter expansion to effectively remove all occurrences of the pattern *
for index in ${!geekletArray[#]}
do
echo "${geekletArray[$index]}" # entire element
echo "${geekletArray[$index]##* }" # id value only
done

how to join two strings to create a variable name

there is an array like this in bash
array_1=(1 2 3 4 5)
array_2=(6 7 8 9 0)
I have another variable that contains 1 or 2 .
array_index=1
So is it possible to create the array name using that variable - like this ?
array_${array_index}[0]
Use variable indirection to read and declare to write:
array_1=(1 2 3 4 5)
array_2=(6 7 8 9 0)
array_index=1
var="array_${array_index}[0]"
echo "The contents of $var is ${!var}"
declare "$var"="Something Else"
echo "Now $var contains ${!var} instead."
This is safer and easier to use correctly than eval.
To create array in bash, exactly to set array's value, you can use eval function, in order to make ${array_index} statement as a real index, so do as follows:
eval array_${array_index}[0]=1
To read value, also eval:
eval echo \${array_${array_index}[0]}
1
Problem is to access a particular element you have to do something like ${array[index]}. But you are also wanting to nest a variable in the array part , which bash won't understand when trying to perform the expansion since it expects array to be a variable.
So the only way I can think of doing this would be to force the array expansion to happen later than your variables. e.g.
> array_1=(1 2 3 4 5)
> array_2=(6 7 8 9 0)
> array_index=1
> eval "echo \${array_$array_index[0]}"
1
As n.m. points out in comments, eval is evil, so you should take care when using it.

Bash Array - Cant get input from a loop in to my array correctly

I run this script as
script.sh 'abcdefghijk123456789!##$%^&' 'aaaa4444ged###'
it should be able to produce an array containing something like
1 1 1 1 15 15 15 15 7 5 23 23 23
with 12 indexes 0 - 11, but what i get is
1 1 1 1 1 5 1 5 1 5 1 5 7 5 2 3 2 3 2 3
with 20 indexes.
I want to populate a array with base numbers for a given char.
So i have a list of chars that we will be using in $startstring ie.
abcc5678% we can say that every char in $startstring is = to one char in the
$charset ie. abcd5678!%. This code finds what each $startstring char is equal
to $charset's index number. That information is what i am trying to capture in a
array. Mostly it works except a bug where instead of the whole number 10 getting
stored in decoded[1] what happens is the number 10 is split in to 1 and 0 and
then both are put in under separate indexes. So instead of a "1 10 1" and 3
indexes i end up with 4 indexes with "1 1 0 1". Im sure im just handling my
variables the wrong way but i searched and searched and now my brain is gonna
explode so i came here for some relief. or hope of it anyway. Can someone tell
me the proper way to insert digits in to this decoded[] array?
#!/bin/bash
#declare -i decoded
charset=$1
startstring=$2
start=$((${#charset}-1))
echo "Setting up CharMap to CharSet"
for i in $(eval echo {0..$start})
do
echo $i " = " ${charset:$i:1}
done
echo "Proving Chars Were Mapped Correctly."
start2=$((${#startstring}))
start3=$((${#charset}-1))
for i in $(eval echo {0..$start2})
do
for p in $(eval echo {0..$start3})
do
if [ "${startstring:$i:1}" == "${charset:$p:1}" ]
then
echo "found that" ${startstring:$i:1}"=" $p 'from the charmap.'
decoded+=$p #<--### I DONT THINK THIS IS WHAT I NEED ###
fi
done
done
##################Just trying to print my new array#########################
start4=$((${#decoded}-1))
echo 'Testing the array $decoded'
echo 'the var start4(length of $decoded) = '$start4
echo 'this number should equal ----------> '$start2
echo 'Printing out the $decoded array in a for loop'
for c in $(eval echo {0..$start4})
do
echo ${decoded[$c]} ###DOESNT WORK LIKE I THOUGHT# also tried echo ${decode:$c:1}
done
decoded+=$p appends $p as a string, not as an array entry. Essentially, you're creating the string "11111515151575232323" by appending all the index numbers together. (Actually, I get "00001414141464322222227" from your example, because of index bound problems. I'll let you worry about that...)
To store the decoded values as an array, set decoded to an empty array before the loop, and then use decoded+=("$p") to add $p as an element:
decoded=() # create an empty array
for i in $(eval echo {0..$start2})
do
for p in $(eval echo {0..$start3})
do
if [ "${startstring:$i:1}" == "${charset:$p:1}" ]
then
echo ${startstring:$i:1} "=" $p
decoded+=("$p") # append $p as a new array element
fi
done
done
Then, to get the size of the array (rather than a string length), use ${#decoded[#]}:
start4=$((${#decoded[#]}-1))

How to find the length of an array in shell?

How do I find the length of an array in shell?
For example:
arr=(1 2 3 4 5)
And I want to get its length, which is 5 in this case.
$ a=(1 2 3 4)
$ echo ${#a[#]}
4
From Bash manual:
${#parameter}
The length in characters of the expanded value of parameter is substituted. If parameter is ‘’ or ‘#’, the value substituted is the
number of positional parameters. If parameter is an array name
subscripted by ‘’ or ‘#’, the value substituted is the number of
elements in the array. If parameter is an indexed array name
subscripted by a negative number, that number is interpreted as
relative to one greater than the maximum index of parameter, so
negative indices count back from the end of the array, and an index of
-1 references the last element.
Length of strings, arrays, and associative arrays
string="0123456789" # create a string of 10 characters
array=(0 1 2 3 4 5 6 7 8 9) # create an indexed array of 10 elements
declare -A hash
hash=([one]=1 [two]=2 [three]=3) # create an associative array of 3 elements
echo "string length is: ${#string}" # length of string
echo "array length is: ${#array[#]}" # length of array using # as the index
echo "array length is: ${#array[*]}" # length of array using * as the index
echo "hash length is: ${#hash[#]}" # length of array using # as the index
echo "hash length is: ${#hash[*]}" # length of array using * as the index
output:
string length is: 10
array length is: 10
array length is: 10
hash length is: 3
hash length is: 3
Dealing with $#, the argument array:
set arg1 arg2 "arg 3"
args_copy=("$#")
echo "number of args is: $#"
echo "number of args is: ${##}"
echo "args_copy length is: ${#args_copy[#]}"
output:
number of args is: 3
number of args is: 3
args_copy length is: 3
Assuming bash:
~> declare -a foo
~> foo[0]="foo"
~> foo[1]="bar"
~> foo[2]="baz"
~> echo ${#foo[*]}
3
So, ${#ARRAY[*]} expands to the length of the array ARRAY.
in tcsh or csh:
~> set a = ( 1 2 3 4 5 )
~> echo $#a
5
In the Fish Shell the length of an array can be found with:
$ set a 1 2 3 4
$ count $a
4
This works well for me:
arglen=$#
argparam=$*
if [ $arglen -eq '3' ];
then
echo Valid Number of arguments
echo "Arguments are $*"
else
echo only four arguments are allowed
fi
For those who still searching a way to put the length of an array into a variable:
foo="${#ARRAY[*]}"

Resources