Я пытаюсь использовать конвейер PowerShell для некоторых повторяющихся задач и проверок, таких как выполнение определенных проверок X раз или переход вперед после того, как ответ в конвейере будет иметь другое состояние.

Самый простой сценарий, который я могу написать для выполнения таких проверок, выглядит примерно так:

do {
    $result=Update-ACMEIdentifier dns1 -ChallengeType dns-01
    if($result.Status -ne 'pending')
    { 
        "Hurray"
        break 
    }

    "Still pending"
    Start-Sleep -s 3
} while ($true)

Вопрос в том, как я могу написать этот скрипт как единый конвейер. Похоже, мне для начала нужен только бесконечный конвейер :

  1..Infinity |
    %{ Start-Sleep -Seconds 1 | Out-Null; $_ } |
    %{Update-ACMEIdentifier dns1 -ChallengeType dns-01 } |
    Select -ExpandProperty Status | ?{$_ -eq 'pending'} |
    #some code here to stop infinity producer or stop the pipeline

Так есть ли какой-нибудь простой однострочник, который позволяет мне разместить производителя бесконечных объектов на одной стороне конвейера?

Хорошим примером такого объекта может быть генератор тиков , который генерирует текущую метку времени в конвейер каждые 13 секунд.

3
Alexey Shcherbak 19 Фев 2016 в 04:23

2 ответа

Лучший ответ

@PetSerAl дал важный указатель в комментарии к вопросу: Блок сценария, содержащий бесконечный цикл, вызываемый с помощью оператор вызова (&) создает бесконечный источник объектов, которые можно отправлять по конвейеру :

& { while ($true) { ... } }

Более поздний сегмент трубопровода может затем остановить трубопровод по требованию.

Примечание :

  • Начиная с PS v5, только Select-Object может напрямую останавливать конвейер .

  • Использование break для остановки конвейера сложно , потому что оно не просто останавливает конвейер, но и вырывается из любого охватывающего цикла - безопасное использование требует оборачивания конвейера фиктивным петля.

  • В качестве альтернативы можно использовать логическую переменную для завершения бесконечного производителя .

Вот примеры, демонстрирующие каждый подход:


Рабочий пример с Select-Object -First :

& { while ($true) { Get-Date; Start-Sleep 1 } } | Select-Object -First 5

Это выполняется Get-Date каждую секунду бесконечно долго, но останавливается Select-Object после 5 итераций.

Эквивалентный пример с break и фиктивным циклом :

do { 
   & { while ($true) { Get-Date; Start-Sleep 1 } } |
     % { $i = 0 } { $_; if (++$i -eq 5) { break } }  # `break` stops the pipeline and
                                                     # breaks out of the dummy loop
} while ($false)

Эквивалентный пример с логической переменной, завершающей бесконечный производитель :

& { while (-not $done) { Get-Date; Start-Sleep 1 } } |
  % { $done = $false; $i = 0 } { $_; if (++$i -eq 5) { $done = $true } }

Обратите внимание, что хотя $done инициализируется только в 2-м сегменте конвейера, а именно в блоке командлета ForEach-Object (%) (неявно) -Begin - эта инициализация все еще выполняется до первого сегмента конвейера - бесконечного производителя - начинает выполняться. Еще раз спасибо, @PetSerAl.

2
Community 23 Май 2017 в 12:31

Не уверен, почему вы хотите использовать конвейер поверх цикла в этом сценарии, но это возможно, используя немного C #; например

$Source = @"
using System.Collections.Generic;
public static class Counter
{
    public static bool Running = false;
    public static IEnumerable<long> Run()
    {
        Running = true;
        while(Running)
        {
            for (long l = 0; l <= long.MaxValue; l++) 
            {
                yield return l;
                if (!Running) {
                    break;
                }
            }
        }
    }
}
"@

Add-Type -TypeDefinition $Source -Language CSharp

[Counter]::Run() | %{
    start-sleep -seconds 1
    $_
} | %{
    "Hello $_"
    if ($_ -eq 12) {
        [Counter]::Running = $false;
    }
}

NB: поскольку числа генерируются параллельно с выполнением конвейера, возможно, что генератор может создать отставание чисел до его остановки. В моем тестировании этого не произошло; но я считаю, что такой сценарий возможен.

Вы также заметите, что я вставил цикл for внутри цикла while; это для гарантии того, что полученные значения действительны; т.е. поэтому я не превышаю максимальное значение для типа данных.


Обновить

Согласно комментарию @ PetSerAl выше, вот адаптированная версия на чистом PowerShell:

$run=$true; &{for($i=0;$run;$i++){$i}} | %{ #infinite loop outputting to pipeline demo
    "hello $_";
        if($_ -eq 10){"stop";$run=$false <# stopping condition demo #>}
}
1
JohnLBevan 19 Фев 2016 в 02:40