Why does my break statement break “too far”?

2019-09-10 00:27发布

Here's a snippet of my code (based on this previous question):

$rgdNames = (Get-AzureRmResourceGroupDeployment -ResourceGroupName "$azureResGrpName").DeploymentName
$siblings = $rgdNames | Where-Object{$_ -match "^($hostname)(\d+)$" }
if ($siblings) {
    # Make a list of all numbers in the list of hostnames
    $serials = @()
    foreach ($sibling in $siblings) {
        # $sibling -split ($sibling -split '\d+$') < split all digits from end, then strip off everything at the front
        # Then convert it to a number and add that to $serials
        $hostnumber = [convert]::ToInt32([string]$($sibling -split ($sibling -split '\d+$'))[1], 10)
        $serials += $hostnumber
    }
    (1..$siblingsMax) | foreach { # Iterate over all valid serial numbers
        if (!$serials.Contains($_)) { # Stop when we find a serial number that isn't in the list of existing hosts
            $serial = $_
             # break # FIXME: For some reason, this break statement breaks "too far"
        }
    }
} else {
    $serial = 1
}
write-output("serial = $serial") # does not print
# ...more statements here, but they're never called :(

I have been looking at it for a while, but can't figure out why the break statement (if un-commented) stops my program instead of just breaking out of its foreach loop. Is there something about foreach that I'm not aware of, or does break just work differently than it would in Java?

For the time being, I'm working around this using an extra if test, and it doesn't mean (too) much that the loop runs its entire length. But it's ugly!

1条回答
一纸荒年 Trace。
2楼-- · 2019-09-10 00:56

This construct:

(1..$siblingsMax) | foreach { # Iterate over all valid serial numbers
    # do stuff
}

is NOT a foreach loop - it's a pipeline with a call to the ForEach-Object cmdlet (aliased as foreach) - and keywords designed to interrupt the control flow of a loop (like break or continue) will NOT act in the same way in these two different cases.

Use a proper foreach loop and break will behave as you expect:

foreach($i in 1..$siblingsMax){
    # do stuff
    if($shouldBreak)
    {
        break
    }
}

Alternatively, you can abuse the fact that continue behaves like you would expect break in a ForEach-Object process block:

(1..$siblingsMax) | foreach { # Iterate over all valid serial numbers
    if($shouldBreak)
    {
        continue
    }
}

although I would strongly discourage this approach (it'll only lead to more confusion)

查看更多
登录 后发表回答