Home > Mobile >  How to make several commands run in their own scope in PowerShell, idiomatically?
How to make several commands run in their own scope in PowerShell, idiomatically?

Time:07-09

Let's say I'm doing some basic string manipulation, as follows:

$segments = $PathToExe -split "\\"
$last = $segments[$segments.Length - 1]
$ProcName = ($last -split "\.")[0]

The purpose of this little piece of code is to get the "name" part of a path to an executable file, to later pass it to the -Name parameter of Get-Process.

Writing this sequence of commands verbatim inside a script is simple enough, but it makes the temporary variables $segments and $last leak into the scope of the script. This may lead to unexpected results when similar variable names are introduced later, or, much worse and much more likely, miscommunicated intent of the variables. Clearly, semantically and functionally confining those variables to the specific task they're performing would improve maintainability of the script.

There are different ways to implement this kind of behavior:

  • Wrap the code into a function:
    function NameFromExePath([string] $Path)
    {
        $segments = $Path -split "\\"
        $last = $segments[$segments.Length - 1]
        return ($last -split "\.")[0]
    }
    
    This is a bit bulky, worsening readability if you do it too often, in the common case where the function code is used only once in the script, right after the function itself.
  • Use a script block:
    $Name = icm {
        $segments = $PathToExe -split "\\"
        $last = $segments[$segments.Length - 1]
        return ($last -split "\.")[0]
    }
    
    This incurs some overhead from icm and feels a bit like a hack. The role of icm is quite different from what it normally is.
  • Delete the variables:
    # Piece of code from beginning of the question goes here
    # <...>
    rv segments
    rv last
    
    This is quite bulky, size scales linearly with the number of variables to remove, and is manual (and so error-prone).

Out of those three approaches (and, hopefully, a much better one I don't know of), which one is the most idiomatic? Or is avoiding variable pollution like this not common practice to begin with?

CodePudding user response:

I believe a scriptblock is the most idiomatic way for creating a new scope. Simply replace Invoke-Command (alias icm) by the call operator &, for less overhead:

$PathToExe = 'c:\foo\test.exe'

$Name = & {
    $segments = $PathToExe -split "\\"
    $last = $segments[$segments.Length - 1]
    ($last -split "\.")[0]  # Implicit output
}

$Name
$segments
$last

Output:

test

Also, you can remove the return statement as any (implicit) output gets captured into the assigned variable.


You can even use the scriptblock to output something to the pipeline:

& {
    foreach( $i in 1..10 ) { $i }
} | Write-Host -ForegroundColor Green

... or pipe something into a scriptblock:

1..10 | & {
    process { $_ * 2 }
} | Write-Host -ForegroundColor Green

This closes the gap between statements and commands, as statements normally cannot be used as part of a pipeline.

  • Related