'for' loop with dynamic array size - bash

I have an array that gets elements added to it when it calls the function findVar. The problem seems to be on the for loop that does not update the number of elements once started running.
When I do echo at the end of the for loop I get the correct number of elements and the last element but it seems not to be checking on the for conditions once started.
for var in "${tempV[#]}"
do
num_words=${#tempV[#]}
let i=i+1
if ! [ $i -gt $num_words ]
then
findVar $objBKP $var
fi
done

Your attempt is looping over every original element in the array and then ensuring that you haven't looped more times than that and calling your function.
That doesn't work because the original expansion of tempV happens once and so the added entries are never seen. But that also doesn't make sense since, by definition, if you are looping over the elements of the array you can't loop more times then there are elements in the array.
What you want to be doing (assuming a non-sparse, integer-indexed array that is only appended to) is looping numerically and checking that you haven't exceeded the array size as the loop condition.
Something like this (untested):
i=0
while [ "$i" -lt "${#tempV[#]}" ]; do
var=${tempV[i]}
findVar "$objBKP" "$var"
i=$((i + 1))
done

You're not using $i for anything other than an iteration counter. It's completely unnecessary in your posted example. Instead, just iterate over the contents of the variable expansion. For example:
for var in "${tempV[#]}"; do
findVar "$objBKP" "$var"
done

Related

How to iterate For loop until certain condition meets?

I need to iterate for loop till certain condition meets in Robot Framework.
${counter}= Set Variable 1
:FOR ${item} IN RANGE ${counter}
\ Check condition
\ ${counter} = ${counter} + 1
Is it possible to increase the ${counter} variable value here?
Yes.
${counter}= Set Variable 1
FOR ${item} IN RANGE 1 20
${counter}= Evaluate ${counter} + 1
Log To Console ${counter}
Exit For Loop If ${counter} == 10
END
And FOR loops can be exited using Exit For Loop or Exit For Loop If keywords. Keywords documentation.
EDIT after comments.
You're asking about a while loop. Robot doesn't have a while loop. There's simply no support for it, and robot probably won't support it until at least 2021. The only looping construct is a for loop.
You seem to have an aversion to setting a limit of 20, but there must be a practical limit to the number of iterations, whether it's 1,000, 10,000, or 1 million or more. Just use a FOR loop that has a huge upper limit, and for all intents and purposes you've created a while loop.
FOR ${item} IN RANGE 1000000
Exit FOR loop if <some condition>
${counter}= evaluate $counter + 1
END
While it doesn't look quite as pretty as While <some condition>, the end result will be the same, assuming your condition becomes true at some point before one million iterations.

How do I get the unique values of a list in bash preserving order and keep the last value for each unique?

I have a list in bash that can have repeated values in it. I would like to remove duplicates and get a list with only the unique values in it. Order must be preserved and the last occurrence of the unique values is the one I wish to keep.
For example, if I have this list:
A=( D B A C D )
I'm looking for this:
result=( B A C D )
I've seen solutions for this when the data is a list in a file, but I'd prefer to keep the list in-memory without jumping through any hoops.
I think I can use an associative array and loop through the list adding the entries as keys in the array and then just dump the keys into the unique list but I'm not an expert with associative arrays across platforms -- do they sort themselves on key value sort of like a lot of C++ STL containers do or do they preserve the order of insertion regardless of key values?
I'd like to avoid a reliance on associative arrays though, because not all systems I may need to run on have bash 4.x or higher... some will be bash 3.x...
Any help would be great.
Without Associative Arrays
You can do it with indexed arrays by using an intermediate indexed array to hold unique values from A. This requires a nested loop over values stored in c[] for each element of A, e.g.
#!/bin/bash
declare -a result # declare result indexed array
declare -a c # declare temp intermediate indexed array
A=( D B A C D ) # original with duplicates
## loop decending over A, reset found flag, loop over c, if present continue,
# otherwise store A at index in c
for ((i = $((${#A[#]}-1)); i >= 0; i--)); do
found=0;
for j in ${c[#]}; do
[ "$j" = "${A[i]}" ] && { found=1; break; }
done
[ "$found" -eq '1' ] && continue
c[i]=${A[i]}
done
## loop over c testing if index for A exists, add from c to result
for ((i = 0; i < ${#A[#]}; i++)); do
[ "${c[i]}" ] && result+=(${c[i]})
done
declare -p result # output result
Example Use/Output
$ bash lastuniqindexed.sh
declare -a result='([0]="B" [1]="A" [2]="C" [3]="D")'
Using Associative Arrays with BASH_VERSION Test
You can do it with a combination of indexed and associative arrays making only a single pass though each array. You use an associative array B keyed with the value of A using B as a frequency array indicating whether an element of A has been seen. You then store the element of A in a temporary indexed array c[] so that the unique values can be added to result preserving the original order.
You can address whether associative array functionality is present with a bash version test at the beginning, e.g.
#!/bin/bash
case $BASH_VERSION in
## empty or beginning with 1, 2, 3
''|[123].*) echo "ERROR: Bash 4.0 needed" >&2
exit 1;;
esac
declare -A B # declare associative array
declare -a result # declare indexed array
A=( D B A C D ) # original with duplicates
## loop decending over A, if B[A] doesn't exist, set B[A]=1, store in c[]
for ((i = $((${#A[#]}-1)); i >= 0; i--)); do
[ -n "${B[${A[i]}]}" ] || { B[${A[i]}]=1; c[i]=${A[i]};}
done
## loop over c testing if index for A exists, add from c to result
for ((i = 0; i < ${#A[#]}; i++)); do
[ "${c[i]}" ] && result+=(${c[i]})
done
declare -p result # output result
Without the use of associative arrays, the nested loops looping over the original checking against each entry in c[] will be much less efficient as the size of the array grows.
Example Use/Output
$ bash lastuniq.sh
declare -a result='([0]="B" [1]="A" [2]="C" [3]="D")'
Look things over and let me know if you have further questions.

How to add 5 to every number of an array with Bash?

I am trying to create an array of user inputs and then add to each element in the array:
read number
for i in 1 2 3
read array[$i]
done
let position=0
for i in "${array[#]}"
do
let array[position]+=($i+$number)
let "position++"
done
for (( i=0; $i<3; i=$1+1 ))
do
echo ${array[$1]}
So, the user will enter "5" for number and then three more numbers for the array(90, 80, 70). The results should be array(95, 85, 75), but the output I'm getting is array(95, 175, 155).
A saner way to write this would be:
read -r number
read -r -a array
for idx in "${!array[#]}"; do
(( array[$idx] += number ))
done
printf '%s\n' "${array[#]}"
Instead of assuming that the indexes start at 0 (which they didn't, originally, because you were explicitly assigning to positions 1, 2 and 3), using "${!array[#]}" finds the actual indexes, and thus works correctly even with sparse arrays or ones not indexed starting at position 0.
Instead of duplicating $i (aka the values from your array) on the right-hand side of +=, it only adds the number itself.
Instead of iterating over indexes (again) to print the values, it just asks the array to dump all its values in index order with "${array[#]}".
See this in operation at https://ideone.com/WTLJSu
There is a behavioral difference insofar as it expects all the array values to be passed on a single line of input. If you don't want that, see the version at https://ideone.com/3OQtt3 instead.

Remove multiple elements from array based on index

I would like to remove multiple elements from an array based on their indexes.
array=("a" "b" "c" "d")
indexes=(1 3)
Output should be
array=("a" "c")
I know how to remove an element from an array knowing the index of the element:
If $i is the index:
array=("${(#)array[1,$i-1]}" "${(#)array[$i+1,$#array]}")
But what if I have multiple elements to remove? If I loop over the array of indexes, once I have removed one element the other indexes won't correspond anymore to the elements to be removed. So how is it possible to do that?
Using BASH arrays you can do this easily:
# original array
array=("a" "b" "c" "d")
# indexes array
indexes=(1 3)
# loop through indexes array and delete from element array
for i in "${indexes[#]}"; do
unset "array[$i]"
done
# check content of original array
declare -p array
declare -a array=([0]="a" [2]="c")
As per Chepner's comments below if OP wants an array of contiguous indices then loop through differential array and populate a new array
# result array
out=()
# loop through differential array and populate result
for i in "${array[#]}"; do
out+=("$i")
done
declare -p out
declare -a out=([0]="a" [1]="c")
Assuming indices is sorted, keep a counter of how many items you have already removed, and subtract that from each index in your loop.
count=0
for i in $indices; do
c=$((i - count))
array=("${(#)array[1,$c-1]}" "${(#)array[$c+1,$#array]}")
count=$((count + 1))
done
In bash, the same approach looks like
count=0
for i in "${indices[#]}"; do
c=$((i - count))
array=( "${array[#]:0:c-1}" "${array[#]:c+1}" )
count=$((count + 1))
done

Adding up variable within loop

I got a number of files and I need to determine how many of those will fit on a 4Tb drive by just knowing first filename. Name pattern is 001j00_rf_geo_????$seqn with sequential 3-digit number at the end. Say I start with 001j00_rf_geo_????100.
block=4000000000000
shopt -s dotglob
seqn="100"
size=`stat -c%s 001j00_rf_geo_????$seqn`
for (( i=$size ;i < $block ; seqn++ ))
do
((size+=$(stat -c%s 001j00_rf_geo_????$seqn)))
done
echo $size
I am pretty sure the summing up part in for loop is wrong. I just could get my head around how to get a total size of files having the the loop part in code.
Look at your for loop, you are not using 'i' at all -- it is unneeded. If you want to use a C-style for loop, you can simply omit the initializer:
for ((; size < block; seqn++))
do
or use a while loop instead
while ((size < block))
do
...
((seqn++))
done
Of course you can just move your initialization to the for loop as well and get rid of the one above
for ((seqn = 100; size < block; seqn++))
do
Give either a try and let me know if you have further questions.

Resources