首先,制作一些示例文件:
2010..2015 | % { "" | Set-Content "example $_.txt" }
#example 2010.txt
#example 2011.txt
#example 2012.txt
#example 2013.txt
#example 2014.txt
#example 2015.txt
我想要做的是将年份与正则表达式捕获组匹配,然后使用
$matches[1]
引用匹配项并使用它。我可以在一个脚本块和一个 cmdlet 中编写它来完成这两项工作,并且它工作正常:gci *.txt | foreach {
if ($_ -match '(\d+)') # regex match the year
{ # on the current loop variable
$matches[1] # and use the capture group immediately
}
}
#2010
#2011
#.. etc
我也可以写这个来在一个脚本块中进行匹配,然后引用
$matches
稍后在另一个 cmdlet 的脚本块中:gci *.txt | where {
$_ -match '(\d+)' # regex match here, in the Where scriptblock
} | foreach { # pipeline!
$matches[1] # use $matches which was set in the previous
# scriptblock, in a different cmdlet
}
它具有相同的输出,并且似乎工作正常。但它是否保证有效,还是未定义和巧合?
可以
'example 2012.txt'
匹配,然后缓冲。 'example 2013.txt'
匹配,然后缓冲。 | foreach
开始工作 'example 2012.txt'
但是 $matches
已经更新为 2013
他们不同步?我不能让它们不同步 - 但我仍然可以依赖未定义的行为。
(FWIW,为了清晰和可读性,我更喜欢第一种方法)。
最佳答案
本身没有同步。第二个示例之所以有效,是因为管道的工作方式。当每个单个对象通过满足 Where-Object
中的条件而被传递时, -Process
阻止 ForEach-Object
立即处理它,所以 $Matches
尚未被其他任何地方覆盖 -match
手术。
如果你要做一些事情导致管道在传递对象之前收集对象,比如排序,你会遇到麻烦:
gci *.txt | where {
$_ -match '(\d+)' # regex match here, in the Where scriptblock
} | sort | foreach { # pipeline!
$matches[1] # use $matches which was set in the previous
# scriptblock, in a different cmdlet
}
例如,上面应该失败,输出 n 个对象,但它们都将是最后一个匹配项。
所以谨慎的做法是不要依赖它,因为它掩盖了危险。其他人(或几个月后的您)可能不会想到要插入
sort
然后对结果感到非常困惑。如 TheMadTechnician在评论中指出,位置会改变事情。将排序放在您引用的部分之后
$Matches
(在 foreach
中),或在使用 where
过滤之前,它仍然会按预期工作。我认为这说明应该避免它,因为它相当不清楚。如果代码在您无法控制的管道部分发生更改,则行为最终可能会出乎意料地不同。
有时我喜欢加入一些详细的输出来演示这一点:
原来的
gci *.txt | where {
"Where-Object: $_" | Write-Verbose -Verbose
$_ -match '(\d+)' # regex match here, in the Where scriptblock
} | foreach { # pipeline!
"ForEach-Object: $_" | Write-Verbose -Verbose
$matches[1] # use $matches which was set in the previous
# scriptblock, in a different cmdlet
}
已排序
gci *.txt | where {
"Where-Object: $_" | Write-Verbose -Verbose
$_ -match '(\d+)' # regex match here, in the Where scriptblock
} | sort | foreach { # pipeline!
"ForEach-Object: $_" | Write-Verbose -Verbose
$matches[1] # use $matches which was set in the previous
# scriptblock, in a different cmdlet
}
您将看到的不同之处在于,在原始版本中,只要
where
“清除”一个对象,foreach
马上得到它。在排序后,可以看到所有的where
s 首先发生,在 foreach
之前得到其中任何一个。sort
没有任何详细的输出,所以我没有费心这样称呼它,但本质上是它的 Process {}
块只是收集所有对象,以便它可以比较(排序!)它们,然后将它们吐出 End {}
堵塞。更多例子
首先,这是一个模拟
Sort-Object
的函数的对象集合(它实际上并不对它们进行排序或做任何事情):function mocksort {
[CmdletBinding()]
param(
[Parameter(
ValueFromPipeline
)]
[Object]
$O
)
Begin {
Write-Verbose "Begin (mocksort)"
$objects = @()
}
Process {
Write-Verbose "Process (mocksort): $O (nothing passed, collecting...)"
$objects += $O
}
End {
Write-Verbose "End (mocksort): returning objects"
$objects
}
}
然后,我们可以在前面的示例中使用它,并在最后进行一些 sleep :
gci *.txt | where {
"Where-Object: $_" | Write-Verbose -Verbose
$_ -match '(\d+)' # regex match here, in the Where scriptblock
} | mocksort -Verbose | foreach { # pipeline!
"ForEach-Object: $_" | Write-Verbose -Verbose
$matches[1] # use $matches which was set in the previous
# scriptblock, in a different cmdlet
} | % { sleep -milli 500 ; $_ }
关于PowerShell: `$matches` 是否保证与管道变量同步执行管道?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40920447/