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:
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.function NameFromExePath([string] $Path) { $segments = $Path -split "\\" $last = $segments[$segments.Length - 1] return ($last -split "\.")[0] }
- Use a script block:
This incurs some overhead from$Name = icm { $segments = $PathToExe -split "\\" $last = $segments[$segments.Length - 1] return ($last -split "\.")[0] }
icm
and feels a bit like a hack. The role oficm
is quite different from what it normally is. - Delete the variables:
This is quite bulky, size scales linearly with the number of variables to remove, and is manual (and so error-prone).# Piece of code from beginning of the question goes here # <...> rv segments rv last
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.