Как выйти из объекта ForEach в PowerShell
У меня есть следующий код:
$project.PropertyGroup | Foreach {
if($_.GetAttribute('Condition').Trim() -eq $propertyGroupConditionName.Trim()) {
$a = $project.RemoveChild($_);
Write-Host $_.GetAttribute('Condition')"has been removed.";
}
};
Вопрос №1: Как выйти из ForEach? Я пробовал использовать "break" и "continue", но он не работает.
Вопрос № 2: я обнаружил, что могу изменить список в цикле foreach
... Мы не можем сделать это как на С#... Почему PowerShell позволяет нам это делать?
Ответы
Ответ 1
Пункт №1. Ввод break
в цикле foreach
завершает цикл, но он не останавливает конвейер. Похоже, вы хотите что-то вроде этого:
$todo=$project.PropertyGroup
foreach ($thing in $todo){
if ($thing -eq 'some_condition'){
break
}
}
Пункт №2. PowerShell позволяет изменять массив в цикле foreach
по этому массиву, но эти изменения не вступают в силу до выхода из цикла. Попробуйте выполнить приведенный ниже код.
$a=1,2,3
foreach ($value in $a){
Write-Host $value
}
Write-Host $a
Я не могу комментировать, почему авторам PowerShell разрешили это, но большинство других языков сценариев (Perl, Python и shell) позволяют создавать аналогичные конструкции.
Ответ 2
Чтобы остановить конвейер, из которого ForEach-Object
является частью, просто используйте оператор continue
внутри блока script под ForEach-Object
. continue
ведет себя по-другому, когда вы используете его в foreach(...) {...}
и ForEach-Object {...}
, и именно поэтому это возможно. Если вы хотите продолжать создавать объекты в конвейере, отбрасывая некоторые из исходных объектов, лучший способ сделать это - отфильтровать с помощью Where-Object
.
Ответ 3
Между foreach
и foreach-object
есть различия.
Очень хорошее описание вы можете найти здесь: MS-ScriptingGuy
Для тестирования в PS у вас есть сценарии, чтобы показать разницу.
ForEach-Object:
# Omit 5.
1..10 | ForEach-Object {
if ($_ -eq 5) {return}
# if ($_ -ge 5) {return} # Omit from 5.
Write-Host $_
}
write-host "after1"
# Cancels whole script at 15, "after2" not printed.
11..20 | ForEach-Object {
if ($_ -eq 15) {continue}
Write-Host $_
}
write-host "after2"
# Cancels whole script at 25, "after3" not printed.
21..30 | ForEach-Object {
if ($_ -eq 25) {break}
Write-Host $_
}
write-host "after3"
Еогеасп
# Ends foreach at 5.
foreach ($number1 in (1..10)) {
if ($number1 -eq 5) {break}
Write-Host "$number1"
}
write-host "after1"
# Omit 15.
foreach ($number2 in (11..20)) {
if ($number2 -eq 15) {continue}
Write-Host "$number2"
}
write-host "after2"
# Cancels whole script at 25, "after3" not printed.
foreach ($number3 in (21..30)) {
if ($number3 -eq 25) {return}
Write-Host "$number3"
}
write-host "after3"
Ответ 4
Чтобы "продолжить" внутри конвейера ForEach-Object
, просто используйте return
. Он делает то, что вы ожидаете от continue
: пропускает остальную часть блока и продолжает следующую итерацию.
Ответ 5
НЕТ прямого способа вырваться из конвейера, однако вы можете применить фильтры, используя Where-Object
. Там, где это возможно, наилучшим решением является преобразование Foreach-Object
(где объекты передаются с использованием конвейера) в стандартную циклическую конструкцию Foreach
, например:
Foreach-Object
(прохождение объектов в конвейере):
1..10 | Foreach-Object {
if ($_ -eq 3) {return;}
£_;
}
Write-Host "Only skips one iteration (iterates all 10 times)";
Преобразовано в стандартную циклическую конструкцию Foreach
:
Foreach ($i in 1..10) {
if ($i -eq 3) {break;}
$i;
}
Write-Host "Ends the loop after 2nd iteration (iterates only 2 times)";
То же самое с использованием фильтра Where-Object
(где преобразование в цикл Foreach
невозможно):
1..10 | Where-Object {$_ -le 2} | Foreach-Object {
$_;
}
Write-Host "Ends the loop after 2nd iteration (iterates only 2 times)";
НТН
Ответ 6
Поскольку ForEach-Object
является командлетом, break
и continue
будут вести себя здесь иначе, чем с ключевым словом foreach
keyword. Оба остановят цикл, но также завершат весь сценарий:
перерыв:
0..3 | foreach {
if ($_ -eq 2) { break }
$_
}
echo "Never printed"
# OUTPUT:
# 0
# 1
продолжают:
0..3 | foreach {
if ($_ -eq 2) { continue }
$_
}
echo "Never printed"
# OUTPUT:
# 0
# 1
До сих пор я не нашел "хорошего" способа разорвать блок сценария foreach без разрыва сценария, кроме "злоупотребляющих" исключений:
бросить:
try {
0..3 | foreach {
if ($_ -eq 2) { throw }
$_
}
} catch { }
echo "End"
# OUTPUT:
# 0
# 1
# End
Альтернативой (что не всегда возможно) будет использование ключевого слова foreach
:
Еогеасп:
foreach ($_ in (0..3)) {
if ($_ -eq 2) { break }
$_
}
echo "End"
# OUTPUT:
# 0
# 1
# End