如何将无限 while 循环转换为管道语句

How to convert infinity while loop to a pipeline statement

我正在尝试使用 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)

问题是 - 如何将此脚本编写为单个管道。 看起来我唯一需要的是 infinity pipeline 开始:

  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

那么有没有任何简单的单线,它允许我将无限对象生产者放在管道的一侧?

此类对象的一个​​很好的例子可能是 tick 生成器,它每 13

将当前时间戳生成到管道中

不确定为什么要在这种情况下通过循环使用管道,但可以通过使用一些 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;
    }
}

注意:因为数字是与管道执行并行生成的,所以生成器可能会在停止之前创建积压的数字。在我的测试中没有发生;但我相信这种情况是可能的。

您还会注意到我在 while 循环中插入了一个 for 循环;那是为了确保产生的值是有效的;也就是说,我不会超出数据类型的最大值。


更新

根据上面@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 #>}
}

@PetSerAl 在对问题的评论中给出了关键指针:A script block containing an infinite loop, invoked with the call operator (&), creates an infinite可以通过管道发送的对象源:

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

随后的管道段可以按需停止管道。

:

  • 从 PS v5 开始,只有 Select-Object 能够直接 停止管道.

    • 可以在我的 this answer 中找到一个 不完美的 通用管道停止函数。
  • 使用break来停止管道是棘手的,因为它不仅会停止管道,还会跳出任何封闭循环- 安全使用需要将管道包裹在一个虚拟循环中。

  • 或者,布尔变量可用于终止无限生成器

以下示例展示了每种方法:


工作示例 Select-Object -First:

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

这会无限期地每秒执行 Get-Date,但在 5 次迭代后被 Select-Object 停止。

具有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 仅在 2nd 管道段中初始化 - 即在 ForEach-Object (%) cmdlet 的 (隐式)-Begin 块 - 初始化仍然发生 第一个管道段 - 无限生产者 - 开始执行之前。再次感谢,@PetSerAl。