关于 Powershell 中的作用域

about scopes in Powershell

我正在学习 Powershell 中的范围并有一些问题:

  1. 关于“局部作用域”:据我所知,局部作用域始终是当前作用域。因此,默认情况下,当我们创建一个项目(没有范围修饰符)时,例如一个变量,在某些范围内,让它是脚本或全局,范围将相应地 script/global 。所以我的问题是:我们什么时候需要明确指定 local 修饰符?
  2. MSDN 说:

You can create a new scope by running a script or function, by creating a session, or by starting a new instance of PowerShell. When you create a new scope, the result is a parent scope (the original scope) and a child scope (the scope that you created). ...
Unless you explicitly make the items private, the items in the parent scope are available to the child scope. However, items that you create and change in the child scope do not affect the parent scope, unless you explicitly specify the scope when you create the items.

但是当我尝试以下操作时:

PS> $Name = "John"
PS> Powershell.exe
PS>echo $Name  // No Output

从上面的引用看来,“启动一个新的 powershell 实例”似乎是一个子作用域,因此父作用域中的所有项目都应该在那里可见。有人可以解释一下吗?

从后一个问题开始:

作用域与函数和调用的脚本 (cmdlet) 一起发挥作用,例如:

Function Test {
    $Test++
    Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test

Returns:

Local: 6
Global: 5

并且:

Function Test {
    $Global:Test++
    Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test

Returns:

Local: 6
Global: 6

或者如果您将函数放在脚本中(例如 MyScript.ps1):

$Test = 5
.\MyScript.ps1
Write-Host $Test # $Test is unaffected unless you use the $Global scope in your script

这将 return 与上面的结果基本相同,除非你 Dot-Source 你的脚本将 运行 在当前范围内:

$Test = 5
. .\MyScript.ps1
Write-Host $Test # $Test might be affected by MyScript.ps1 if you just use $Test

你在做什么:
您正在创建一个全新的 PowerShell 会话(使用 Powershell.exe),它将以新的变量列表开始。
请注意,如果您在新会话中 exit,您将再次看到初始变量:

PS C:\> $Name = "John"
PS C:\> Powershell.exe
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> Write-Host 'New session' $Name
New session
PS C:\> Exit
PS C:\> Write-Host 'Initial session' $Name
Initial session John

关于第一个问题,我不认为有很多应用程序需要显式引用 $Local 范围,但举个例子,你可能会使用它:

$Test = 5
Function Test {
    Write-Host ($Local:Test++)
}
Test

在上面的示例中,如果您显式使用 $Local 范围,则 unary increment operator 将以 0 开头(实际上,您从一个空的局部变量开始,该变量将转换为 0) 和 5 如果您省略 $Local 范围,您将在父范围之外继承 $Test 变量的副本。

补充

  1. [...] when will we need to explicitly specify the local modifier?

$local: 很少 需要 ,因为在没有范围说明符。

但是,仅当引用变量实际作为 local 变量 存在时才适用,因为 PowerShell 的 动态作用域 使来自祖先(父) 作用域的变量也对后代(子)作用域可见(参见获取更多信息):

  • 例如,假设您在 global 范围内声明了 $foo = 'bar',然后在 [=68] 中引用 $foo =]script 会先寻找 local $foo 实例;如果存在 none,则使用 ancestral(父级)作用域中定义的 $foo,如果有的话,它将是 global $foo 在此示例中,将返回 'bar'

  • 相比之下,如果在您的脚本中使用 $local:foo,而没有定义本地 $foo 变量,则默认情况下您会得到 $null 或,如果 Set-StrictMode -Version 2 或更高版本有效,则会发生 语句 终止错误。


  1. MSDN says: [...] by creating a session, or by starting a new instance of PowerShell [...] the result is a parent scope (the original scope) and a child scope (the scope that you created).

documentation is incorrect in this regard as of this writing (a GitHub issue已提交):

  • 范围之间的祖先(parent-child)关系仅存在于给定会话(运行空间).

    • 也就是说,动态作用域 - 变量和其他来自祖先作用域的定义的可见性 - 仅适用于给定会话中的作用域。

    • 一个值得注意的例外是来自模块的函数在子作用域中做运行调用范围 - 除非调用范围恰好是 global 范围;模块有自己的 范围域 (技术上称为 会话状态 ),仅链接到全局范围 - 请参阅 this GitHub docs issue 进行讨论.

  • 因此,调用作用域的子作用域在以下场景中创建,其中新启动的代码对调用范围内的变量(和其他定义)一无所知:

    • 通过 PowerShell 远程启动 新会话(例如,使用 Enter-PSSession) or Invoke-Command -Computer

    • 在 v7.0+

      中使用 Start-Job or Start-ThreadJob or running threads in parallel with ForEach-Object -Parallel 启动 后台 [线程] 作业
    • 启动一个新的 PowerShell 实例(进程),使用 PowerShell CLIpwsh for PowerShell [Core],powershell.exe 对于 Windows PowerShell)。

    • 在这些情况下,要将值从调用范围传递到新启动的代码,需要明确的操作:

      • 当调用 CLI 或使用 Start-Job 时,在同一台机器上创建子进程 process 时,只有 environment调用进程中定义的变量自动可供子进程使用。
      • 否则,来自调用者的值必须作为 参数 传递,或者 - 使用 CLI 时除外 - 通过 $using: 范围 - 参见