如何将无限 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。
我正在尝试使用 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。