PowerShellPracticeAndStyle icon indicating copy to clipboard operation
PowerShellPracticeAndStyle copied to clipboard

Indentation for lines after a pipe character

Open JohnLBevan opened this issue 2 years ago • 3 comments
trafficstars

When we have multiple commands, each piping their output to one another, for readability / single-line width it's generally best to put each command on a different line; like so:

Write-Verbose 'Before the stuff in the piped code'
$myArrayOfThings |
    Where-Object {$_.SomeText -like 'pattern*'} |
    ForEach-Object {[PSCustomObject]@{
        MyText = $_.SomeText -replace '^pattern', ''
        AnotherProperty = $_.SomethingElse
    }}
Write-Verbose 'No longer in the piped code'

Should we define a best practice / guideline for this scenario?

Generally I indent all lines after the initial pipe to show they're a continuation of the previous line. However I don't indent again after subsequent lines because there would be no additional benefit.

The downside with this approach is that there's no "closing line"; which looks nasty if you the following line is a close for a parent block; e.g.

if ($someFlag) {
    $myArrayOfThings |
        Where-Object {$_.SomeText -like 'pattern*'} |
        ForEach-Object {[PSCustomObject]@{
            MyText = $_.SomeText -replace '^pattern', ''
            AnotherProperty = $_.SomethingElse
        }}
} # here we 're now have 8 spaces between this line's indentation level and the preceding line's.

An option to tidy this up could be to put a comment to represent the close of he piped "block" / maybe with something after the comment character to show it's a close for the piped input; e.g.

if ($someFlag) {
    $myArrayOfThings |
        Where-Object {$_.SomeText -like 'pattern*'} |
        ForEach-Object {[PSCustomObject]@{
            MyText = $_.SomeText -replace '^pattern', ''
            AnotherProperty = $_.SomethingElse
        }}
    # |
}

Another option could be to put parentheses around the block.

if ($someFlag) {
    ($myArrayOfThings |
        Where-Object {$_.SomeText -like 'pattern*'} |
        ForEach-Object {[PSCustomObject]@{
            MyText = $_.SomeText -replace '^pattern', ''
            AnotherProperty = $_.SomethingElse
        }}
    )
}

Do others have any thoughts or preferences on how best to approach these scenarios?

JohnLBevan avatar Oct 23 '23 14:10 JohnLBevan

That's definitely how I prefer to see it handled:

  • Wrap on pipe (if the line's long enough to wrap)
  • Indent if you wrap on pipeline.

I guess I'm used to Python and Yaml, but I do not worry about the lack of the closing line.

I would not use parenthesis to try and show the end of a block, if only because they have side effects. This, for example, would result in outputting $Moved.

($Moved = Get-ChildItem $SourceFolder |
    Where Length -gt 100mb |
    Move-Item -Destination $TargetFolder -Passthru
)

Jaykul avatar Oct 24 '23 03:10 Jaykul

Thanks @Jaykul

Agreed; an assignment within brackets has side effects / for that (if adopting the brackets approach) I'd place the bracket after the equals sign:

$Moved = (
    Get-ChildItem $SourceFolder |
    Where Length -gt 100mb |
    Move-Item -Destination $TargetFolder -Passthru
)

... but you're right that the approach of using brackets may lead to people placing the bracket before the assignment and being caught out.

JohnLBevan avatar Oct 24 '23 07:10 JohnLBevan

But it's more than that. Parentheses are also blocking. Compare:

1..100 | 
    ForEach { start-sleep -m 100; $_ }
(1..100 | 
    ForEach { start-sleep -m 100; $_ }
)

Jaykul avatar Oct 25 '23 00:10 Jaykul