Populate array in a for loop - arrays

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:)

Related

Output on one line all elements of an array that satisfy a condition using BASH

The following is to be implemented in a BASH script. I have access to what I think are other "devices" commonly used in bash scripts such as sed, awk, bc, dc etc. I have an array whose elements are strings. The elements are either "none" or a name. This question is in 2 parts. The first goes over the simplest form of the problem with as little background as possible. It is all that is needed I think. The second part is almost entirely background on what I am trying to do in general and may be useful, but I do not think is needed necessarily.
Part 1: SHORT VERSION
I have an array of strings
An example of this array is
array=('none' 'none' 'name1' 'none' 'name2' 'none' 'name3' 'none' 'name4')
In my problem the array is much longer. I would like to search if specific elements are 'none' or 'name#' (i.e., not 'none'). AND What I need is to start at element i and check every nth element after that to see if it is 'none' or 'name#' and print all elements that are 'name#' on one line
For this example that means if I want to output starting from say element 2 (array starts at 0) and then checking every 3rd element after that:
my output for this would be name1 name4
if I had started at element 1 then my output would have been name3
I do not understand piping sufficiently well to be able to echo with a condition (2 conditions really, the first being the periodic range of elements, the second the name vs none criteria).
Part 2: LONGER VERSION (NOT NECCESSARY)
I am running a double do loop that calls a program. every once in a while the output is not what I want, so I flag the run name and part of that run that I didn't like. I have this criteria set up it saves the "i" "j" in the name so I know where the failure occurred.
Since bash does arrays and not matrices, I save these failed states in an array that is i * j long. This is why I need to access a certain section. If each run has 3 parts, then in post analysis, for that run, I search the elements that correspond to that loop. i.e. if there are 3 runs with 3 parts in each, then in my array elements 0-2 are run 1, 3-5 are run 2, and 6-8 are run 3. This corresponds to the array I have shown above.
each run has a part 1, 2 and 3 in this example, or in bash syntax a part 0,1 and 2. (i.e. elements 0,3,6 is the first parts of runs 0 1 2 respectively in the above array, 1,4,7 is the second parts of runs 0,1,2 respectively, and elements 2,5,8 are the 3rd parts of runs 0,1,2 respectively). For each set of parts I want to print out all elements corresponding to those parts that are not 'none'
Here is a dummy code snippet of the process (and this works fine, the question pertains to the post analysis part after)
k=0
for ((i=0 ; i<$NRuns ; i++)); do
for ((j=0 ; j<$NPartsPerRun ; j++)); do
k=$((k + 1))
#PSEUDO CODE HERE not actual bash
call program
if "program output bad" ; then
array[$k]=$Runname
else
echo "Thanks for taking the time to look at my problem"
fi
done
done
Now for post analysis, I want to know for a given part, which runs failed. i.e for a given "j" what were the runnames of the "i's" that failed
Post analysis (and my issue. The code is a sorry attempt to convey the idea, it is really more of a pseudo code, it is not bash)
for ((i=0 ; i<$NPartsPerRun ; i++)); do
#PSEUDO CODE HERE not actual bash
#first element to check is $i, then check every Xth element
if ${array[$i]} || ${array[$(i+X)]} etc.. != 'none' echo "element names that pass condition test"
fi
done
done
If I understood the question correctly, you could emulate a matrix with an associative array and make your code much more readable:
declare -A matrix=()
# save runs into the matrix
for ((i=0; i<n_runs; i++)); do
for ((j=0; j<n_parts; j++)); do
[[ $(program) = bad ]] && matrix[$i,$j]=$name
done
done
# print unsuccessful runs for a given part
part=2 runs=
for ((i=0; i<n_runs; i++)); do
[[ ${matrix[$i,$part]} ]] && runs+="${matrix[$i,$part]} "
done
printf '%s\n' "${runs% }"
I don't know if it solves your problem but I think that it would be something close to this.
array=('none' 'none' 'name1' 'none' 'name2' 'none' 'name3' 'none' 'name4')
step=3
echo "array size = ${#array[#]}"
echo "step = $step"
for ((i= 0; i < step; i += 1)); do
echo -ne "i = $i: [ "
for ((j = i; j < ${#array[#]}; j += step)); do
if [[ "${array[$j]}" != "none" ]]; then
echo -n "${array[$j]} "
fi
done
echo "]"
done
This will output:
array size = 9
step = 3
i = 0: [ name3 ]
i = 1: [ name2 ]
i = 2: [ name1 name4 ]
This works by using a nested loop to check for the indexes of the form $i + n * $step.
You could, of course, change the way it's printing the values to suit your use case. I hope it answers at least the short version of your question!

bash function returns with changed indexes

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

Shell Array Cleared for Unknown Reason [duplicate]

This question already has answers here:
A variable modified inside a while loop is not remembered
(8 answers)
Closed 6 years ago.
I have a pretty simple sh script where I make a system cat call, collect the results and parse some relevant information before storing the information in an array, which seems to work just fine. But as soon as I exit the for loop where I store the information, the array seems to clear itself. I'm wondering if I am accessing the array incorrectly outside of the for loop. Relevant portion of my script:
#!/bin/sh
declare -a QSPI_ARRAY=()
cat /proc/mtd | while read mtd_instance
do
# split result into individiual words
words=($mtd_instance)
for word in "${words[#]}"
do
# check for uboot
if [[ $word == *"uboot"* ]]
then
mtd_num=${words[0]}
index=${mtd_num//[!0-9]/} # strip everything except the integers
QSPI_ARRAY[$index]="uboot"
echo "QSPI_ARRAY[] at index $index: ${QSPI_ARRAY[$index]}"
elif [[ $word == *"fpga_a"* ]]
then
echo "found it: "$word""
mtd_num=${words[0]}
index=${mtd_num//[!0-9]/} # strip everything except the integers
QSPI_ARRAY[$index]="fpga_a"
echo "QSPI_ARRAY[] at index $index: ${QSPI_ARRAY[$index]}"
# other items are added to the array, all successfully
fi
done
echo "length of array: ${#QSPI_ARRAY[#]}"
echo "----------------------"
done
My output is great until I exit the for loop. While within the for loop, the array size increments and I can check that the item has been added. After the for loop is complete I check the array like so:
echo "RESULTING ARRAY:"
echo "length of array: ${#QSPI_ARRAY[#]}"
for qspi in "${QSPI_ARRAY}"
do
echo "qspi instance: $qspi"
done
Here are my results, echod to my display:
dev: size erasesize name
length of array: 0
-------------
mtd0: 00100000 00001000 "qspi-fsbl-uboot"
QSPI_ARRAY[] at index 0: uboot
length of array: 1
-------------
mtd1: 00500000 00001000 "qspi-fpga_a"
QSPI_ARRAY[] at index 1: fpga_a
length of array: 2
-------------
RESULTING ARRAY:
length of array: 0
qspi instance:
EDIT: After some debugging, it seems I have two different arrays here somehow. I initialized the array like so: QSPI_ARRAY=("a" "b" "c" "d" "e" "f" "g"), and after my for-loop for parsing the array it is still a, b, c, etc. How do I have two different arrays of the same name here?
This structure:
cat /proc/mtd | while read mtd_instance
do
...
done
Means that whatever comes between do and done cannot have any effects inside the shell environment that are still there after the done.
The fact that the while loop is on the right hand side of a pipe (|) means that it runs in a subshell. Once the loop exits, so does the subshell. And all of its variable settings.
If you want a while loop which makes changes that stick around, don't use a pipe. Input redirection doesn't create a subshell, and in this case, you can just read from the file directly:
while read mtd_instance
do
...
done </proc/mtd
If you had a more complicated command than a cat, you might need to use process substitution. Still using cat as an example, that looks like this:
while read mtd_instance
do
...
done < <(cat /proc/mtd)
In the specific case of your example code, I think you could simplify it somewhat, perhaps like this:
#!/usr/bin/env bash
QSPI_ARRAY=()
while read -a words; do␣
declare -i mtd_num=${words[0]//[!0-9]/}
for word in "${words[#]}"; do
for type in uboot fpga_a; do
if [[ $word == *$type* ]]; then
QSPI_ARRAY[mtd_num]=$type
break 2
fi
done
done
done </proc/mtd
Is this potentially what you are seeing:
http://mywiki.wooledge.org/BashFAQ/024

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

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))

Resources