What is the setlocal / endlocal equivalent for PowerShell?

后端 未结 4 2273
时光说笑
时光说笑 2021-02-15 15:24

Objective

Isolate environmental variable changes to a code block.

Background

If I want to create a batch scrip

4条回答
  •  Happy的楠姐
    2021-02-15 15:38

    In batch files, all shell variables are environment variables too; therefore, setlocal ... endlocal provides a local scope for environment variables too.

    By contrast, in PowerShell, shell variables (e.g., $var) are distinct from environment variables (e.g., $env:PATH) - a distinction that is generally beneficial.

    Given that the smallest scope for setting environment variables is the current process - and therefore the entire PowerShell session, you must manage a smaller custom scope manually, if you want to do this in-process (which is what setlocal ... endlocal does in cmd.exe, for which PowerShell has no built-in equivalent; to custom-scope shell variables, use & { $var = ...; ... }):

    In-process approach: manual management of a custom scope:

    To ease the pain somewhat, you can use a script block ({ ... }) to provide a distinct visual grouping of the command, which, when invoked with & also create a new local scope, so that any aux. variables you define in the script block automatically go out of scope (you can write this as a one-line with ;-separated commands):

    & { 
      $oldVal, $env:MYLANG = $env:MYLANG, 'EN'
      my-cmd dostuff -o out.csv
      $env:MYLANG = $oldVal 
    }
    

    More simply, if there's no preexisting MYLANG value that must be restored:

    & { $env:MYLANG='EN'; my-cmd dostuff -o out.csv; $env:MYLANG=$null }
    

    $oldVal, $env:MYLANG = $env:MYLANG, 'EN' saves the old value (if any) of $env:MYLANG in $oldVal while changing the value to 'EN'; this technique of assigning to multiple variables at once (known as destructuring assignment in some languages) is explained in Get-Help about_Assignment_Operators, section "ASSIGNING MULTIPLE VARIALBES".

    A more proper and robust but more verbose solution is to use try { ... } finally { ... }:

    try {
      # Temporarily set/create $env:MYLANG to 'EN'
      $prevVal = $env:MYLANG; $env:MYLANG = 'EN'
    
      my-cmd dostuff -o out.csv  # run the command(s) that should see $env:MYLANG as 'EN'
    
    } finally { # remove / restore the temporary value
      # Note: if $env:MYLANG didn't previously exist, $prevVal is $null,
      #       and assigning that back to $env:MYLANG *removes* it, as desired.
      $env:MYLANG = $prevVal
    }
    

    Note, however, that if you only ever call external programs with the temporarily modified environment, there is no strict need for try / catch, because external programs never cause PowerShell errors as of PowerShell 7.1, though that may change in the future.

    To facilitate this approach, this answer to a related question offers convenience function
    Invoke-WithEnvironment
    , which allows you to write the same invocation as:

    # Define env. var. $env:MYLANG only for the duration of executing the commands
    # in { ... }
    Invoke-WithEnvironment @{ MYLANG = 'EN' } { my-cmd dostuff -o out.csv }
    

    Alternatives, using an auxiliary process:

    By using an auxiliary process and only setting the transient environment variable there,

    • you avoid the need to restore the environment after invocation

    • but you pay a performance penalty, and invocation complexity is increased.

    Using an aux. cmd.exe process:

    cmd /c "set `"MYLANG=EN`" & my-cmd dostuff -o out.csv"
    

    Note:

    • Outer "..." quoting was chosen so that you can reference PowerShell variables in your command; embedded " must then be escaped as `"

    • Additionally, the arguments to the target command must be passed according to cmd.exe's rules (makes no difference with the simple command at hand).

    Using an aux. child PowerShell session:

    # In PowerShell *Core*, use `pwsh` in lieu of `powershell`
    powershell -nop -c { $env:MYLANG = 'EN'; my-cmd dostuff -o out.csv }
    

    Note:

    • Starting another PowerShell session is expensive.

    • Output from the script block ({ ... }) is subject to serialization and later deserialization in the calling scope; for string output, that doesn't matter, but complex objects such as [System.IO.FileInfo] deserialize to emulations of the originals (which may or may not be problem).

提交回复
热议问题