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.
CodePudding user response:
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.
- The first section declares variables, in this case we define
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 differentWrite-
cmdlets, output streams, and redirection.
- In most cases,
$()
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 the1
literally.
- After the
>>
we use another sub-expression to insert the value of the$i
th 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.
- While simple variable expansion would insert the
- Returning an object without assigning to a variable, such as this string, is effectively the same thing as using
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.
CodePudding user response:
Okey,
I fixed that;
$a=@('ab','bc','cd','dc')
$a.Length
foreach ($number in $a) {
$numberofIIS = $a.IndexOf($number)
Write-Host ($numberofIIS,">>>",$number)
}
CodePudding user response:
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
}