PowerShell:`$ matches`是否保证与管道变量同步进行管道传输?

Tes*_*ler 5 powershell

首先,制作一些示例文件:

2010..2015 | % { "" | Set-Content "example $_.txt" }

#example 2010.txt                                                                          
#example 2011.txt                                                                          
#example 2012.txt                                                                          
#example 2013.txt                                                                          
#example 2014.txt                                                                          
#example 2015.txt
Run Code Online (Sandbox Code Playgroud)

我想要做的是将年份与正则表达式捕获组匹配,然后引用匹配$matches[1]并使用它.我可以写这个在一个scriptblock中,在一个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
Run Code Online (Sandbox Code Playgroud)

我也可以写这个来在一个scriptblock中进行匹配,然后$matches在另一个cmdlet的scriptblock中引用:

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
}
Run Code Online (Sandbox Code Playgroud)

它具有相同的输出,似乎工作正常.但它是否有保证可行,或者它是不确定的和巧合的?

可以'example 2012.txt'匹配,然后缓冲.'example 2013.txt'匹配,然后缓冲.| foreach开始工作,'example 2012.txt'$matches已经更新,2013他们不同步?

我不能让它们失去同步 - 但我仍然可以依赖于未定义的行为.

(FWIW,我更喜欢清晰度和可读性的第一种方法).

bri*_*ist 7

本身没有同步.第二个例子是有效的,因为管道的工作方式.当每个单个对象通过满足条件传递时Where-Object,该-ProcessForEach-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
}
Run Code Online (Sandbox Code Playgroud)

例如,上面应该失败,输出n个对象,但它们都将是最后一个匹配.

所以谨慎不要依赖它,因为它掩盖了危险.其他人(或者你几个月后)可能不会想到任何插入a 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
}
Run Code Online (Sandbox Code Playgroud)

排序

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
}
Run Code Online (Sandbox Code Playgroud)

你会看到的不同之处在于,在原版中,只要where"清除"一个对象,立即foreach获取它.在排序中,您可以whereforeach获取任何内容之前先查看所有发生的事情.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我们可以使用前面的示例,最后一些睡眠:

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 ; $_ }
Run Code Online (Sandbox Code Playgroud)

  • 即如果cmdlet仅从它们的`process {}`块输出,那么我在不同位置的匹配/ $匹配对将通过管道是单线程工作,但如果它们从`end {}`输出则匹配/ $不同地方的比赛将无法奏效.由于这种区别取决于了解每个cmdlet的内部工作原理,因此不保证可以使用,不要使用."*它打破了封装和模块化原则,将cmdlet视为隔离组件*".谢谢@briantist. (2认同)