Powershell "private" scope 似乎一点用都没有

Powershell "private" scope seems not useful at all

我有下面的脚本,来自互联网:

$private:a = 1
Function test  {
    "variable a contains $a"
    $a = 2
    "variable a contains $a"
}
test

它打印 2。没问题。如果我删除 "private",如下所示:

$a = 1
Function test  {
    "variable a contains $a"
    $a = 2
    "variable a contains $a"
}

它仍然打印“2”。好像没什么区别。您能否提供 "private" 范围如何影响结果的快速示例?

谢谢。

注意:
* 这个答案解释了为什么 OP 的代码以它的方式运行(并且它按设计运行);此外,它还提供了一些有关 PowerShell 中变量作用域 的一般信息。
* 对于作用域 重要的实际使用 private,见

您的第一个片段打印:

variable a contains
variable a contains 2

您的第二个片段打印:

variable a contains 1
variable a contains 2

在第一个片段中,使用范围 private 导致父(脚本)范围的 a 变量对子(函数)范围隐藏 ,按照设计,所以第一行输出显示 $ano value
(未定义的变量具有值 $null,其计算结果为字符串上下文中的空字符串)。

相比之下,在第二个片段中,没有 private 范围修饰符,父范围的变量 a 对子范围 可见

在 PowerShell 中,函数默认在 child 作用域中执行。

因此,在上面的两个片段中,赋值给函数内部的变量$a隐式地创建了一个local $a 那里的变量,其范围仅限于封闭函数。

换句话说:

  • 赋值给$a 在函数中创建一个函数局部名为 $a 的变量,然后 shadows(隐藏)script-level $a 变量(如果不是已经被声明为 $private:a) 隐藏 - 尽管请注意 PowerShell 中的 local 意味着 child 作用域 do 看它的价值;请参阅下一节。
  • 离开函数后,$a 再次拥有其原始的脚本级值。

PowerShell

中有关变量范围 的一般信息

注:

  • 讨论集中在变量,但原则上它适用于所有范围的实体,即也适用于函数别名、PowerShell 驱动器 和模块。然而,只有变量允许修改祖先作用域中的实例。

  • 讨论仅限于在给定会话的主运行空间(其主线程)中运行的代码,因此确实[=141] =]不适用于out-of-runspace上下文,即:

    • 远程 调用(例如通过 Invoke-Command -ComputerName),后台作业(以 Start-Job)、线程作业Start-ThreadJob、PSv6+)和基于线程的并行处理(ForEach-Object -Parallel、PSv7+)。

    • 这样的上下文不与主运行空间共享状态,只有变量可以传递给他们,通过$using:范围;有关详细信息,请参阅

  • 与大多数 shell 一样,PowerShell 使用 dynamic 而不是 lexical 范围.

    • 也就是说,变量的可见性取决于运行时调用堆栈[,而不是仅对封闭的词法结构可见,例如函数定义=289=]。举一个简单的例子:如果函数 A 定义了一个变量,然后调用函数 B,则 B 默认看到该变量。换句话说:给定函数或脚本看到的变量取决于调用它的人
  • 另请参阅:概念性 about_Scopes 帮助主题。

概览:

  • 除非变量显式隐藏在作用域 private 中,后代作用域 请参阅 该变量读取它的值使用变量名没有范围限定符(例如,$a)或需要Get-Variable -Scope

    • 例如,$foo = 'bar'; function Get-Foo { $foo }; Get-Foo 输出 'bar',因为函数运行的子范围看到调用者的 $foo 变量..

    • 请注意,虽然后代作用域不会看到 $private: 创建的变量的值默认情况下,他们仍然可以使用 relative 跨范围访问来引用它们,使用 Get-Variable -ScopeSet-Variable -Scope.
      非相对范围修饰符($script$global$local)通常起作用-except 如果引用发生在创建私有变量的同一范围内,并且范围修饰符恰好有效地针对同一范围,例如 $local:privateVarName 始终如此.

  • 赋值给一个不合格的变量,然而,在current (local) scope,它可以 shadow 祖先作用域中的同名变量。

    • 也就是说,$a = 2$local:a = 2 隐式相同。
    • 例如,$foo = 'bar'; function Get-Foo { $foo = 'bar2'; $foo }; Get-Foo; $foo 输出 bar2bar,因为不合格的赋值 $foo = 'bar2' 创建了 local $foo 函数内部的变量(然后 shadows 函数内部调用者的 $foo),保持调用者的 $foo 不变。
  • 要显式获取/修改祖先作用域中的变量,请使用Get-Variable / Set-Variable -Scope <n> <name>,其中<n>表示作用域级别, 0 代表当前作用域,1 代表父作用域,依此类推。
    请注意 Get-Variable returns 默认情况下是一个 [System.Management.Automation.PSVariable] 实例,因此为了仅获取 ,请访问其 .Value 属性,或使用 -ValueOnly 开关,它只有 returns 开头的值。

    • 函数陷阱处理程序中,创建本地一个变量的副本,您也可以 在定义它的最直接的祖先作用域中修改一个变量,如下所示:

      • ([ref] $var).Value = ...
      • (如果并且一旦创建了一个同名的局部变量,以上将仅修改 local 变量。)
    • script范围和global范围中的变量可以也可以通过使用 $script:$global: 范围修饰符 来访问和修改;例如,$script:a$global:a
      请注意,$script: 指的是(立即)封闭脚本文件的 顶级范围。

  • Modules each have their own scope domain (aka session state), which is linked only to the global scope. That is, modules see outside variables only from the global scope, not from a caller in any other scope, such as from a script (the exception is if the caller is from the same module); this can cause unexpected behavior with preference variables, as discussed in this GitHub issue.

    • 简而言之:
      • 所有非模块代码在(相同的)范围域中运行。
      • 每个模块都有自己的作用域。
      • 所有范围域共享的唯一范围是唯一的全局范围,它作为所有范围的根范围域。
  • Set-Variable -Option AllScope 声明变量允许在 任何后代中读取 和修改 =289=] 作用域 无需限定名称 ;换句话说:只有一个单个同名变量存在,任何作用域都可以使用非限定变量名直接读写。

    • 没有单独的 -Scope 参数,-Option AllScope 应用于 current 范围内的变量(例如,脚本范围在脚本的顶层,函数内部函数的局部作用域)。因此,要安全地创建一个 script-全局变量,您可以访问不合格的读取 写入,请使用 Set-Variable -Scope Script -Option AllScope.

    • -Scope Global 不同于 -Option AllScope:而 -Scope Global 创建一个全局可访问的变量,读取它 may,修改它does,需要$global:作用域修饰符,没有-Option AllScope,全局变量可以shadowed 被后代作用域中的同名变量覆盖。另请注意,全局变量是 session-global,因此即使在定义它的脚本终止后它仍然存在。

    • 通过结合-Scope Global-Option AllScope你有效地创建了一个session-global singleton variable 可以从任何范围读取和写入而无需限定符;然而,如前所述,即使在脚本退出后,这样的变量仍然存在。

私有范围在编写调用用户提供的回调的函数时很有用。考虑这个简单的例子:

filter Where-Name {
    param(
        [ScriptBlock]$Condition
    )
    $FirstName, $LastName = $_ -split ' '
    if(&$Condition $FirstName $LastName) {
        $_
    }
}

那么,如果有人这样称呼它:

$FirstName = 'First2'
'First1 Last1', 'First2 Last2', 'First3 Last3' |
  Where-Name {param($a, $b) $a -eq $FirstName}

他们希望只看到 First2 Last2 行,但实际上这将打印所有三行。 这是因为 $FirstName 变量发生了冲突。 为防止此类冲突,您可以将 Where-Name 中的变量声明为私有的:

filter Where-Name {
    param(
        [ScriptBlock]$private:Condition
    )
    $private:FirstName, $private:LastName = $_ -split ' '
    if(&$Condition $FirstName $LastName) {
        $_
    }
}

现在 Where-Name 中的 $FirstName 在从 $Condition 脚本块引用时不会在外部范围内隐藏 $FirstName

好的软件设计意味着最小化耦合(除其他外)。在 Powershell 中,这包括在每个变量上使用 private。如果你想让某个值在随后调用的模块中可用,请显式传递该信息。不这样做应该有一个很好的 EXCEPTION 理由,因为每次你依赖隐式知识(例如,当你不使用私有变量时在 Powershell 中发生的那种),你会增加以后出现意外错误的机会(也许几个月后,当软件中包含更多代码时)。