是否可以在Powershell中有条件地进行管道,即仅在满足条件时才执行管道元素?

Adi*_*bar 13 powershell conditional pipeline pipe filter

我想做这样的事情:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>
Run Code Online (Sandbox Code Playgroud)

<statement>的结果通过<filter1>运行,然后只有满足<condition>才会运行<filter2>,然后通过剩余的过滤器运行,无论是否应用了<filter2>.这相当于:

if (<condition>) {
  <statement> | <filter1> | <filter2> | <filter3> | <filter4> | <filter5>
} else {
  <statement> | <filter1> | <filter3> | <filter4> | <filter5>
}
Run Code Online (Sandbox Code Playgroud)

这在仅在调用某个开关时将给定过滤器应用于结果集的函数中非常有用.如果条件过滤器在长流水线中早期发生,则使用外部if块编写它会导致大量重复代码,尤其是在存在多个条件过滤器的情况下.

这是一个例子.以下函数显示给定帐户在给定目录子树中Show-AccountPerms \\SERVERX\Marketing DOMAIN\jdoe具有的权限(例如,在\ SERVERX\Marketing下的目录树中提供用户DOMAIN\jdoe具有的权限报告).

function Show-AccountPerms {
    param (
        [parameter(mandatory = $true)]$rootdir,
        [parameter(mandatory = $true)]$account,
        [switch]$files,
        [switch]$inherited
    )
    gci -r $rootdir `
    |where {$_.psiscontainer} `
    |foreach {
        $dir = $_.fullname
        (get-acl $_.pspath).access `
        | where {$_.isinherited -eq 'False'} `
        |foreach {
            if ($_.identityreference -eq $account) {
                "{0,-25}{1,-35}{2}" -f $_.identityreference, $_.filesystemrights, $dir
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,它仅显示显式权限(由| where {$_.isinherited -eq 'False'}过滤器强制执行),并且仅显示目录(由|where {$_.psiscontainer}过滤器强制执行).

但是,|where {$_.psiscontainer}如果调用-files开关,我想忽略,如果调用了-inherited开关,则忽略| where {$_.isinherited -eq 'False'}.使用外部if块实现此功能将使代码翻两番,并且几乎75%的代码将重复执行.有没有办法保持这些过滤器在线,但指示powershell只应用它们相应的开关是假的?

请注意,这只是一个示例,因此我对此功能特有的任何变通方法都不感兴趣.我正在寻找有关管道有条件的一般问题的答案,而不是如何完成这项特定任务的解决方案.

zda*_*dan 7

您可以测试过滤器中的两个条件,如果其中任何一个为真,则允许对象沿着管道向下.如果您的"条件"位于-or操作员的左侧,$true如果您不希望测试过滤条件,请将其结果.

要使用您的示例:

| where {$_.psiscontainer}
Run Code Online (Sandbox Code Playgroud)

变为:

| where {$files -or $_.psiscontainer}
Run Code Online (Sandbox Code Playgroud)

和

| where {$_.isinherited -eq 'False'}
Run Code Online (Sandbox Code Playgroud)

变

| where {$inherited -or $_.isinherited -eq 'False'}
Run Code Online (Sandbox Code Playgroud)

概括:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>
Run Code Online (Sandbox Code Playgroud)

变为:

<statement> | <filter1> | <-not condition -or filter2> | <filter3> | <filter4> | <filter5>
Run Code Online (Sandbox Code Playgroud)


Adi*_*bar 5

抱歉,我并不是有意放弃这个问题。发布的答案并不是我想要的,但我在发布后不久就找到了一种方法,并且很长一段时间没有回到该网站。由于尚未发布解决方案,这就是我想出的解决方案。这并不完全是我提出问题时的想法,而且也不太漂亮,但显然这是唯一的方法:

<statement> | <filter1> | foreach {if (<condition>) {$_ | <filter2>} else {$_} | <filter3> | <filter4> | <filter5>
Run Code Online (Sandbox Code Playgroud)

因此,在示例中,该行

|where {$_.psiscontainer} `
Run Code Online (Sandbox Code Playgroud)

将更改为

|foreach {if (-not $files) {$_ | where {$_.psiscontainer}} else {$_}} `
Run Code Online (Sandbox Code Playgroud)

和

|where {$_.isinherited -eq 'False'} `
Run Code Online (Sandbox Code Playgroud)

将更改为

|foreach {if (-not $inherited) {$_ | where {$_.isinherited -eq 'False'}} else {$_}} `
Run Code Online (Sandbox Code Playgroud)

(是的,通常我会把它写成|foreach {if ($files) {$_} else {$_ | where {$_.psiscontainer}}},|foreach {if ($inherited) {$_} else {$_ | where {$_.isinherited -eq 'False'}}}但为了清楚起见,我这样做了。)

我希望可能有更优雅的东西,可以评估过滤器前面的条件一次,以确定是执行还是跳过管道的一个阶段。像这样的东西:

<statement> | <filter1> | if (<condition>) {<filter2>} | <filter3>
Run Code Online (Sandbox Code Playgroud)

( 的特殊情况if,不是通常的含义;可以使用不同的关键字),或者也许

<statement> | <filter1> | (<condition>) ? <filter2> | <filter3>
Run Code Online (Sandbox Code Playgroud)

$_在条件中将无效,除非它是在当前管道之外定义的,例如,如果管道包含在语句内switch,$_则在<condition>will 中引用switch语句的$_.

我想我会向微软提出一个功能建议。这不仅会使代码更加优雅,而且也会更加高效,因为如果它是内置功能,则可以对整个管道<condition>进行一次评估,而不是在每次迭代中测试相同的独立条件。


Mar*_*ica 5

我认为你的意思是我刚刚炮制的以下内容:

function Pipe-If([ScriptBlock]$decider, [ScriptBlock]$pipeElement)
{
    if (&$decider) {
        $pipeElement
    } else {
        {$input}
    }
}

@(1,2,3) | &(Pipe-If {$doDouble} {$input | % { $_ * 2} })
Run Code Online (Sandbox Code Playgroud)

$doDouble如果is 则结果为 2, 4, 6 $true,如果为$false则结果为 1, 2, 3。

这里的关键是任意管道元素(如 )% { $_ * 2}可以封装为 ScriptBlock as {$input | % { $_ * 2 } },并且可以通过前置 来将其转换回管道元素&。

我使用https://devblogs.microsoft.com/powershell/diy-ternary-operator来获取灵感。


重要的提示。 不要使用这样的东西:

filter Incorrect-Pipe-If([ScriptBlock]$decider, [ScriptBlock]$pipeElement) {
    if (&$decider) {
        $_ | &$pipeElement
    } else {
        $_
    }
}

@(1,2,3) | Incorrect-Pipe-If {$doDouble} {$_ | % { $_ * 2} }
Run Code Online (Sandbox Code Playgroud)

这会导致%执行多次,对管道中的每个对象执行一次。 Pipe-If仅正确执行该%命令一次,并向其发送整个对象流。

在上面的例子中这不是问题。但如果命令是tee bla.txt,那么区别就很重要。