Koans/Foundations/AboutLoopsAndPipelines.Koans.ps1
#Requires -Module PSKoans [Koan(113)] param() <# The Pipeline & Loops A core tenet of PowerShell scripts is its pipeline. It's mainly just a fancy way of iterating over an array of items, so we'll also cover standard loops as well to give a comparison. Unlike standard loops, where altering the collection you're iterating through is considered a terrible idea, so much so that many languages (including PowerShell) actually throw an error if you try, pipelines are designed to break apart a collection into its parts and operate on the pieces one at a time. As such, modifying the 'collection' mid-pipeline is very common. Pipelines are commonly used to take an input and perform multiple actions before storing or outputting the result. #> Describe 'Pipelines and Loops' { It 'is used to process input in several stages' { # The .. operator is used to quickly create arrays with a range of numbers $Values = 1..5 # Where-Object is a filtering cmdlet, used to discard selected objects mid-pipeline. $Values | Where-Object {$_ -lt 3} | Should -Be @(1, 2) } It 'works with many cmdlets to efficiently process input' { # The pipeline takes each element of the array and feeds them one by one into the next cmdlet 1..5 | ForEach-Object { <# ForEach-Object is a cmdlet that utilises the pipeline to create a sort of loop, where each object that it receives from the pipeline has the same set of actions performed on it. In pipeline contexts, you will frequently see $_ or $PSItem variables used. These are automatic variables used to denote 'the current object in the pipeline'. #> $_ | Should -Be $PSItem <# Expressions not stored in a variable are dropped back to the output stream. In pipelines, this means that they are passed along to the next pipeline command. If a pipeline ends and it is not stored in a variable, all output will eventually end up in the main output stream. In the console, this will be displayed on screen. #> $_ + 2 } | Should -Be __ # What happens to the array after we change each value? # Values can be stored at the end of a pipeline by storing the entire pipeline sequence in a # variable. This will create an array of the final values. $Strings = 1..10 | ForEach-Object {"Hello $_!"} | # Line breaks after a pipe character are OK! Where-Object {$_ -notlike '*5*'} # (Indents are optional.) $Strings | Should -Be __ } It 'is like a specialised loop' { # Standard loops are also available in PowerShell, but unlike other languages we can still utilise # PowerShell's output stream to bundle all their output. $Values = foreach ($Number in 1..5) { # In these kinds of loops, the specified variable name ($Number in this case) takes the place # of the $_ or $PSItem automatic variables instead. $Number++ } $Values | Should -Be __ $Values = for ($i = 0; $i -lt 5; $i++) { # For loops are quite rare in native PowerShell code. They have their uses, but are # frequently too semantically obscure to have a place in PS's verbose ecosystem. $i } $Values | Should -Be @(0, 1, 2, 3, 4) $Values = while ($true) { # watch out for infinite loops! $Tick++ # Remember: an undeclared variable acts as zero until we increment it! if ($Tick -gt 2) { break # the break statement breaks out of the current loop. } } $Values | Should -Be __ <# There are some other types of loops available, but these are the main ones. The reverse of a while loop is an until loop, e.g.: until ($condition -eq $true) { Do-Things } Similarly, Do..While and Do..Until loops are just like their standard counterparts, but will always execute the loop at least once. #> $Values = do { 'eat me!' } while ($false) # had we put our while at the head of the loop, there would be zero iterations $Values | Should -Be __ } } |