Is it possible to pipe conditionally in Powershell, i.e. execute an element of a pipeline only if a condition is met?

你说的曾经没有我的故事 提交于 2019-11-30 11:38:41

You can test for both conditions in your filter allowing the object down the pipeline if either one is true. If your "condition" is on the left side of the -or operator, make it result to $true if you don't want your filter condition tested.

To use your example:

| where {$_.psiscontainer}

becomes:

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

and

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

becomes

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

To generalise:

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

becomes:

<statement> | <filter1> | <-not condition -or filter2> | <filter3> | <filter4> | <filter5>

I think the other answer to this question misunderstands what is being asked.

The solution lies in the following:

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

What this will do, is pass all elements through to the next filter, EXCEPT those that match "Something", in which case it does different logic. The logic can be changed to make it pass an altered version of the pipeline element instead of a function.

I think you mean something like the following, which I just concocted:

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

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

results in 2, 4, 6 if $doDouble is $true, and on $false it results in 1, 2, 3.

The key here is that an arbitrary pipe element like % { $_ * 2} can be encapsulated as a ScriptBlock as {$input | % { $_ * 2 } }, and that it can be converted back to a pipe element by prepending &.

I used http://blogs.msdn.com/b/powershell/archive/2006/12/29/dyi-ternary-operator.aspx for inspiration.


Important note. Don't use something like this:

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

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

This causes % to be executed multiple times, once for each object in the pipeline. Pipe-If correctly executes the % command just once, and sends it the entire stream of objects.

In the example above that is not a problem. But if the command is tee bla.txt then the difference is important.

Adi Inbar

Sorry, I didn't mean to abandon this question. The answers that were posted weren't what I was driving at, but I figured out a way to do it shortly after posting, and didn't come back to the site for a long time. Since a solution hasn't been posted, here's what I came up with. It's not quite what I had in mind when I asked the question and it isn't too pretty, but apparently it's the only way to do it:

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

So, in the example, the line

|where {$_.psiscontainer} `

would be changed to

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

and

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

would be changed to

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

(Yes, normally I'd write that as |foreach {if ($files) {$_} else {$_ | where {$_.psiscontainer}}}, and |foreach {if ($inherited) {$_} else {$_ | where {$_.isinherited -eq 'False'}}} but I did it this way for clarity.)

I was hoping there might be something more elegant, that would evaluate a condition in front of the filter once to determine whether to execute or skip a stage of the pipeline. Something like this:

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

(a special case of if, not the usual meaning; a different keyword could be used), or maybe

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

$_ would be invalid in the condition, unless it's defined outside the current pipeline, for example if the pipeline is contained within a switch statement, $_ in the <condition> would refer the switch statement's $_.

I think I'll make a feature suggestion to Microsoft. This would not only make the code more elegant, it would be more efficient as well, because if it's a built-in feature, <condition> could be evaluated once for the entire pipeline, rather then testing the same independent condition in each iteration.

Another option is to use a global preference flag (of type System.Management.Automation.ActionPreference) to allow the user to determine whether the pipeline filter does something.

For example, the $progressPreference can be set to the following values:

  • SilentlyContinue
  • Stop
  • Continue
  • Inquire
  • Ignore

This preference flag is then used by Write-Progress to determine the desired behavior.

for example, if you have a pipeline filter Show-Progress, that counts items and displays a progress bar, then it will only display this progress bar when $progressPreference is set to Continue.

You can use simular construction in your own pipeline filters.

This is similar to Marnix Klooster's answer but simpler to work with. The advantage of this formulation over that one is in the syntax of the code to be executed. It's far closer to a normal pipeline block. Basically you just need to enclose whatever you want in {} (braces).

Note that, Like Marnix's script this is a blocking function. The pipeline results are collected into $Input and the function itself executes only once. Only $pipeElement code ever executes more than once and then only if -Execute is true.

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

Using this function does not require it be defined already! You really can actually define it within the function where you want to use it and let it disappear when your function completes.

The -execute parameter enables/disables step execution. Any expression with a boolean result works. For example $(1 -eq 0) works just like $false.

@(1,2,3)|Conditional_Block $true  {?{$_ -le 2}}|%{"'$_'"}
@(1,2,3)|Conditional_Block $false {?{$_ -le 2}}|%{"'$_'"}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!