How to iterate dynamically in an array in bash - arrays

I have an array in my script which I want to use it in for, like this:
for j in "${list[#]}"
do
func $j
done
In function func, sometimes another member will add to the list array, but the for iterates as many time as it was initiated(before the for started)
I want "for" to iterate based on the updated array content
some lines of that function:
if [ $s1 -gt 0 ]
then
(( k = $k +1 ))
list[$k]=$id2
fi

As Eric Renouf said, modifying the list you're working on can be tricky. As long as you're only appending new elements (to the end of the list), and just want those new elements included in the iteration, you can use something like this:
for ((i=0; i<${#list[#]}; i++)); do
#...
if (( s1 > 0 )); then
list+=( "$id2" )
fi
done
Since the length of the list (${#list[#]}) is recalculated every time around, the loop will include new elements. Also, the +=( ) syntax guarantees you're always strictly appending.

It appears that k is the index of the last element, meaning your function only appends items to the end of the list. It seems the best option is to iterate while a separate counter is less than k.
i=0
while (( i < k )); do
j=${list[i]}
func "$j"
((i++))
done

Related

Perl: grep from multiple arrays at once

I have multiple arrays (~32). I want to remove all blank elements from them. How can it be done in a short way (may be via one foreach loop or 1-2 command lines)?
I tried the below, but it's not working:
my #refreshArrayList=("#list1", "#list2", "#list3","#list4", "#list5", "#list6" , "#list7");
foreach my $i (#refreshArrayList) {
$i = grep (!/^\s*$/, $i);
}
Let's say, #list1 = ("abc","def","","ghi"); #list2 = ("qwe","","rty","uy", "iop"), and similarly for other arrays. Now, I want to remove all blank elements from all the arrays.
Desired Output shall be: #list1 = ("abc","def","ghi"); #list2 = ("qwe","rty","uy", "iop") ### All blank elements are removed from all the arrays.
How can it be done?
You can create a list of list references and then iterator over these, like
for my $list (\#list1, \#list2, \#list3) {
#$list = grep (!/^\s*$/, #$list);
}
Of course, you could create this list of list references also dynamically, i.e.
my #list_of_lists;
push #list_of_lists, \#list1;
push #list_of_lists, \#list2;
...
for my $list (#list_of_lists) {
#$list = grep (!/^\s*$/, #$list);
}
#$_ = grep /\S/, #$_ for #AoA; # #AoA = (\#ary1, \#ary2, ...)
Explanation
First, this uses the statement modifier, "inverting" the usual for loop syntax into the form STMT for LIST
The for(each) modifier is an iterator: it executes the statement once for each item in the LIST (with $_ aliased to each item in turn).
It is mostly equivalent to a "normal" for loop, with the notable difference being that no scope is set and so there is no need to tear it down either, adding a small measure of efficiency.† Ae can have only one statement; but then again, that can be a do block. Having no scope means that we cannot declare lexical variables for the statement (unless a do block is used).
So the statement is #$_ = grep /\S/, #$_, executed for each element of the list.
In a for(each) loop, the variable that is set to each element in turn as the list is iterated over ("topicalizer") is an alias to those elements. So changing it changes elements. From perlsyn
If VAR is omitted, $_ is set to each value.
If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop.
In our case $_ is always an array reference, and then the underlying array is rewritten by dereferencing it (#$_) and assigning to that the output list of grep, which consists only of elements that have at least one non-space character (/\S/).
† I ran a three-way benchmark, of the statement-modifier loop against a "normal" loop with and without a topical variable.
For adding 100e6 numbers I get 8-11% speedup (on both a desktop and a server) and with a more involved calculation ($r = ($r + $_ ) / sqrt($_)) it's 4-5%.
A side observation: In both cases the full for loop without a variable (using default $_ for the topicalizer) is 1-2% faster than the one with a lexical topical variable set.

Powershell - print the list length respectively

I want to write two things in Powershell.
For example;
We have a one list:
$a=#('ab','bc','cd','dc')
I want to write:
1 >> ab
2 >> bc
3 >> cd
4 >> dc
I want this to be dynamic based on the length of the list.
Thanks for helping.
Use a for loop so you can keep track of the index:
for( $i = 0; $i -lt $a.Count; $i++ ){
"$($i + 1) >> $($a[$i])"
}
To explain how this works:
The for loop is defined with three sections, separated by a semi-colon ;.
The first section declares variables, in this case we define $i = 0. This will be our index reference.
The second section is the condition for the loop to continue. As long as $i is less than $a.Count, the loop will continue. We don't want to go past the length of the list or you will get undesired behavior.
The third section is what happens at the end of each iteration of the loop. In this case we want to increase our counter $i by 1 each time ($i++ is shorthand for "increment $i by 1")
There is more nuance to this notation than I've included but it has no bearing on how the loop works. You can read more here on Unary Operators.
For the loop body itself, I'll explain the string
Returning an object without assigning to a variable, such as this string, is effectively the same thing as using Write-Output.
In most cases, Write-Output is actually optional (and often is not what you want for displaying text on the screen). My answer here goes into more detail about the different Write- cmdlets, output streams, and redirection.
$() is the sub-expression operator, and is used to return expressions for use within a parent expression. In this case we return the result of $i + 1 which gets inserted into the final string.
It is unique in that it can be used directly within strings unlike the similar-but-distinct array sub-expression operator and grouping operator.
Without the subexpression operator, you would get something like 0 + 1 as it will insert the value of $i but will render the + 1 literally.
After the >> we use another sub-expression to insert the value of the $ith index of $a into the string.
While simple variable expansion would insert the .ToString() value of array $a into the final string, referencing the index of the array must be done within a sub-expression or the [] will get rendered literally.
Your solution using a foreach and doing $a.IndexOf($number) within the loop does work, but while $a.IndexOf($number) works to get the current index, .IndexOf(object) works by iterating over the array until it finds the matching object reference, then returns the index. For large arrays this will take longer and longer with each iteration. The for loop does not have this restriction.
Consider the following example with a much larger array:
# Array of numbers 1 through 65535
$a = 1..65535
# Use the for loop to output "Iteration INDEXVALUE"
# Runs in 106 ms on my system
Measure-Command { for( $i = 0; $i -lt $a.Count; $i++ ) { "Iteration $($a[$i])" } }
# Use a foreach loop to do the same but obtain the index with .IndexOf(object)
# Runs in 6720 ms on my system
Measure-Command { foreach( $i in $a ){ "Iteration $($a.IndexOf($i))" } }
Another thing to watch out for is that while you can change properties and execute methods on collection elements, you can't change the element values of a non-collection collection (any collection not in the System.Concurrent.Collections namespace) when its enumerator is in use. While invisible, foreach (and relatedly ForEach-Object) implicitly invoke the collection's .GetEnumerator() method for the loop. This won't throw an error like in other .NET languages, but IMO it should. It will appear to accept a new value for the collection but once you exit the loop the value remains unchanged.
This isn't to say the foreach loop should never be used or that you did anything "wrong", but I feel these nuances should be made known before you do find yourself in a situation where a better construct would be appropriate.
Okey,
I fixed that;
$a=#('ab','bc','cd','dc')
$a.Length
foreach ($number in $a) {
$numberofIIS = $a.IndexOf($number)
Write-Host ($numberofIIS,">>>",$number)
}
Bender's answer is great, but I personally avoid for loops if at all possible. They usually require some awkward indexing into arrays and that ugly setup... The whole thing just ends up looking like hieroglyphics.
With a foreach loop it's our job to keep track of the index (which is where this answer differs from yours) but I think in the end it is more readable then a for loop.
$a = #('ab', 'bc', 'cd', 'dc')
# Pipe the items of our array to ForEach-Object
# We use the -Begin block to initialize our index variable ($x)
$a | ForEach-Object -Begin { $x = 1 } -Process {
# Output the expression
"$x" + ' >> ' + $_
# Increment $x for next loop
$x++
}
# -----------------------------------------------------------
# You can also do this with a foreach statement
# We just have to intialize our index variable
# beforehand
$x = 1
foreach ($number in $a){
# Output the expression
"$x >> $number"
# Increment $x for next loop
$x++
}

Inconsistencies when removing elements from a Bash array

When using bash arrays, you can remove an element in the following manner:
unset array[i] # where i is the array index
The problem with this is after the unset ${array[#]] is not truly valid. Yes
it does give you the number of active array elements, but not the actual array
depth. The index of the removed index in the array still exists. It simply has
been set inactive/null. For example:
declare -a array=( a b c d e )
unset array[2]
for ((i=0; i < ${#array[#]}; i++)) ; do
echo "$i: ${array[$i]}"
done
outputs the following:
0: a
1: b
2:
3: d
array[2] is still there but set to null or inactive
array[4] (e) does not show because ${#array[#]} is the number of active
array elements and not the true array element depth. This gets very messy very
quickly each time an element is unset
As an example consider the following code:
# array contains 0 or more strings
# remove looks for and removes a given string
remove () {
local str=$1
for (( i = 0 ; i < ${#array[#]}; i++ )) ; do
if [[ "${array[$i]}" == "$str" ]] ; then
unset array[$i]
return 0
fi
done
echo "$str: not registered"
return 0
}
This is only valid the first time remove is called. After that, valid
elements may be missed.
One fix for this is to added the following line after the unset:
unset array[$i]
+ array=( "${array[#]}" )
This re-initializes array with the element completely removed.
The problem, this feels kludgy.
So my questions are this:
1) is there a way of getting the true array element depth?
2) is there a way of detecting the end of an array when iterating through it?
3) is there another cleaner solution?
Understanding Why for ((i=0; i<${#array[#]}; i++)) is broken
There's no such thing as "true array element depth" in the sense that you're asking for here. Bash arrays aren't really arrays -- they're hash maps (numerically indexed for regular arrays, indexed by strings for bash 4.0's new "associative arrays"). As such, there's absolutely no reason for the keys to start from 0 -- you can have an array like the following: declare -a array=( [1000]="one thousand" [2000]="two thousand" [3000]="three thousand" ), and its length is exactly 3; there aren't a bunch of NUL/empty elements sitting between those items (looked up with keys 1000, 2000 and 3000, respectively).
Removing Elements From A Sparse Array Safely
Iterate over indices, if you want to remove by index. Whereas "${array[#]}" iterates over items in the array, "${!array[#]}" (note the !) iterates over by the indices by which those items can be looked up.
As you've observed, it's unsafe to assume that the indices range from 0 to the total number of items in an array, as bash arrays are allowed to be sparse -- but there's no reason to write your code to make that assumption in the first place.
for array_idx in "${!array[#]}"; do
unset "array[$array_idx]"
done

Loop through array, assign new value to variable in array

In my script I ask the user for some input and save it into some variables. Before using the strings in the variables I want to cut them if they are too long and add an ellipsis at the end.
So I put all of the variables in an array and send them through a loop and an if statement and reasign the new value to the variable in the array. I have tried many ways and none have worked. The following being an example:
preset1="Short string"
preset2="Not long very string"
preset3="A very long string here which we will then cut"
presets=("${preset1}" "${preset2}" "${preset3}")
for x in "${presets[#]}"; do
if [[ "${#x}" -gt 20 ]]; then
y="${x:0:20}..."
presets[$x]="$y"
fi
done
Please help!
You have to loop over indexes of your array in order to change the values:
for x in "${!presets[#]}"; do
str=${presets[$x]}
(( ${#str} > 20 )) && presets[$x]="${str:0:20}..."
done
Works for associative and sparse arrays as well.
For variety, you can also use only parameter expansion like this:
for x in "${!presets[#]}"; do
str=${presets[$x]}
suffix=${str:20}
presets[$x]=${str:0:20}${suffix:+...}
done
You need to use the array[i] syntax to assign to array elements:
for ((i = 0; i < ${#presets[#]}; ++i)); do
x=${presets[i]}
if [[ "${#x}" -gt 20 ]]; then
y="${x:0:20}..."
presets[i]="$y"
fi
done

How do i create array and put value on it - BASH script

ahhh array and loops my weakest links. I was trying to create array depending on user input so
printf "%s\n" "how may array you want"
read value
after this i will ask what value user want to put on a array(this is the bit im stuck on)
i=1
while [ $i -le $value ]
do
echo "what value you want to put in array $i"
read number
echo $number >> array.db
i=$(( i+1 ))
echo
done
although this method works(i think) but i'm not too sure if i'm actually creating an array and putting value to that array.
you can expand arrays in bash dynamically. you can use this snippet
a=(); a[${#a[#]}]=${number}; echo ${a[#]}
The first statement defines an empty array. with the second (which you can use in your while loop) you insert a value at last elment position + 1, due to ${#a[#]} represents the length of a. the third statement just prints all elements in the array.

Resources