15

我想做这样的事情:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>

<statement> 的结果通过 <filter1> 运行,然后它们仅在满足 <condition> 时运行通过 <filter2>,然后通过其余过滤器,无论是否应用 <filter2>。这相当于:

if (<condition>) {
  <statement> | <filter1> | <filter2> | <filter3> | <filter4> | <filter5>
} else {
  <statement> | <filter1> | <filter3> | <filter4> | <filter5>
}

这在仅当调用某个开关时才将给定过滤器应用于结果集的函数中很有用。如果条件过滤器出现在长管道的早期,使用外部 if 块编写它会导致大量代码重复,尤其是在有多个条件过滤器的情况下。

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

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
            }
        }
    }
}

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

|where {$_.psiscontainer}但是,如果调用 -files 开关,我想忽略,如果调用 -inherited 开关,我想忽略| where {$_.isinherited -eq 'False'}。使用外部 if 块完成此操作将使代码翻两番,其中几乎 75% 将是重复。有没有办法让这些过滤器保持一致,但指示 powershell 只应用它们对应的开关是假的?

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

4

6 回答 6

7

您可以在过滤器中测试这两个条件,如果其中一个为真,则允许对象通过管道。如果您的“条件”在运算符的左侧,如果您不想测试您的过滤条件-or,请使其结果。$true

要使用您的示例:

| where {$_.psiscontainer}

变成:

| where {$files -or $_.psiscontainer}

| where {$_.isinherited -eq 'False'}

变成

| where {$inherited -or $_.isinherited -eq 'False'}

概括地说:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>

变成:

<statement> | <filter1> | <-not condition -or filter2> | <filter3> | <filter4> | <filter5>
于 2012-07-23T22:50:10.153 回答
4

对不起,我不是故意放弃这个问题。发布的答案不是我想要的,但我在发布后不久就想出了一个方法,并且很长时间没有回到该网站。由于尚未发布解决方案,这就是我想出的。当我问这个问题时,这并不是我的想法,也不是很漂亮,但显然这是唯一的方法:

<statement> | <filter1> | foreach {if (<condition>) {$_ | <filter2>} else {$_} | <filter3> | <filter4> | <filter5>

因此,在示例中,该行

|where {$_.psiscontainer} `

将更改为

|foreach {if (-not $files) {$_ | where {$_.psiscontainer}} else {$_}} `

|where {$_.isinherited -eq 'False'} `

将更改为

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

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

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

<statement> | <filter1> | if (<condition>) {<filter2>} | <filter3>

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

<statement> | <filter1> | (<condition>) ? <filter2> | <filter3>

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

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

于 2013-07-24T03:56:33.780 回答
4

认为您的意思类似于以下内容,这是我刚刚炮制的:

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

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

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

这里的关键是可以将任意管道元素 like% { $_ * 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} }

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

在上面的示例中,这不是问题。但是,如果命令是,tee bla.txt那么差异重要。

于 2013-11-21T14:41:17.543 回答
4

我认为这个问题的另一个答案误解了所问的内容。

解决方法在于:

... | %{if($_ -match "Something"){DoSomethingWith $_ }else{$_}} | ...

这将做的是将所有元素传递到下一个过滤器,除了那些匹配“Something”的元素,在这种情况下它会执行不同的逻辑。可以更改逻辑以使其传递管道元素的更改版本而不是函数。

于 2012-07-25T14:46:29.137 回答
1

这类似于 Marnix Klooster 的答案,但使用起来更简单。这个公式相对于那个公式的优势在于要执行的代码的语法。它更接近普通的管道块。基本上你只需要在 {} (大括号)中包含你想要的任何东西。

请注意,与 Marnix 的脚本一样,这是一个阻塞函数。管道结果被收集到 $Input 中,函数本身只执行一次。只有 $pipeElement 代码会多次执行,并且只有在 -Execute 为真时才执行。

function Conditional_Block([bool]$Execute,[ScriptBlock]$PipeElement)
{
    if ($Execute) 
        {$ExecutionContext.InvokeCommand.NewScriptBlock("`$(`$Input|$PipeElement)").invoke()}
    else
        {$input}
}

使用此功能不需要已经定义!你真的可以在你想要使用它的函数中定义它,并在你的函数完成时让它消失。

-execute 参数启用/禁用步骤执行。任何具有布尔结果的表达式都有效。例如 $(1 -eq 0) 就像 $false 一样工作。

@(1,2,3)|Conditional_Block $true  {?{$_ -le 2}}|%{"'$_'"}
@(1,2,3)|Conditional_Block $false {?{$_ -le 2}}|%{"'$_'"}
于 2018-06-05T22:23:48.107 回答
0

另一种选择是使用全局首选项标志(类型System.Management.Automation.ActionPreference)来允许用户确定管道过滤器是否执行某些操作。

例如,$progressPreference可以设置为以下值:

  • 默默地继续
  • 停止
  • 继续
  • 查询
  • 忽视

然后使用此首选项标志Write-Progress来确定所需的行为。

例如,如果您有一个管道过滤器Show-Progress,它计算项目并显示一个进度条,那么它只会在$progressPreference设置为时显示这个进度条Continue

您可以在自己的管道过滤器中使用模拟构造。

于 2014-11-15T01:26:02.660 回答