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

时间:2016-02-19 01:23:32

标签: powershell pipeline

我试图将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

那么是否有任何简单的单行,允许我将无限对象生成器放在管道的一侧?

此类对象的良好示例可能是 tick生成器,每 13 秒生成当前时间戳到管道中

2 个答案:

答案 0 :(得分:2)

@PetSerAl在对该问题的评论中给出了关键指针:包含无限循环的脚本块,使用调用运算符(&)调用,创建了无限的对象源,可以通过管道发送

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

答案 1 :(得分:1)

在这种情况下,不确定为什么要在循环中使用管道,但可以使用一些C#; e.g。

$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循环;这是为了确保所产生的价值是有效的;即所以我不会超出数据类型的最大值。

<强>更新

Per @ 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 #>}
}