当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 关于范围

作者:精品下载站 日期:2024-12-14 02:18:44 浏览:16 分类:玩电脑

关于范围


简短描述

解释 PowerShell 中范围的概念并演示如何设置和更改元素的范围。

详细描述

PowerShell 通过限制读取和更改的位置来保护对变量、别名、函数和 PowerShell 驱动器 (PSDrive) 的访问。 PowerShell 使用范围规则来确保您不会无意中更改其他范围中的项目。

范围规则

当您启动 PowerShell 时,主机 (pwsh.exe) 会创建一个 PowerShell 运行空间。主机进程可以有多个运行空间。每个运行空间都有自己的会话状态和范围容器。无法跨运行空间实例访问会话状态和范围。

以下是范围的基本规则:

  • 作用域可以嵌套。外部作用域称为父作用域。任何嵌套作用域都是该父作用域的子作用域。
  • 除非您明确将其设为私有,否则项目在其创建的范围和任何子范围中都是可见的。
  • 您可以为当前范围之外的范围声明变量、别名、函数和 PowerShell 驱动器。
  • 您在某个范围内创建的项目只能在创建它的范围内进行更改,除非您明确指定不同的范围。
  • 当运行空间中运行的代码引用某个项目时,PowerShell 会搜索作用域层次结构,从当前作用域开始,然后遍历每个父作用域。

    • 如果未找到该项目,则会在当前范围内创建一个新项目。
  • 如果找到匹配项,则会从找到的范围中检索该项目的值。
  • 如果更改该值,该项目将复制到当前范围,以便更改仅影响当前范围。
  • 如果您显式创建一个与不同范围内的项目共享名称的项目,则原始项目可能会被新项目隐藏,但不会被覆盖或更改。
  • 父级和子级范围

    您可以通过调用脚本或函数来创建新的子作用域。调用作用域是父作用域。被调用的脚本或函数是子作用域。您调用的函数或脚本可能会调用其他函数,从而创建子作用域的层次结构,其根作用域是全局作用域。

    笔记

    模块中的函数不会在调用作用域的子作用域中运行。模块有自己的会话状态,该状态链接到模块导入的范围。所有模块代码都在特定于模块的范围层次结构中运行,该范围具有自己的根范围。有关更多信息,请参阅本文的模块部分。

    创建子作用域时,它包括具有 AllScope 选项的所有别名和变量,以及一些自动变量。本文稍后将讨论此选项。

    除非您明确将项目设为私有,否则父范围中的项目可供子范围使用。您在子范围中创建或更改的项目不会影响父范围,除非您在创建项目时显式指定范围。

    要查找特定范围内的项目,请使用 Get-VariableGet-Alias 的 Scope 参数。

    例如,要获取本地范围内的所有变量,请键入:

    Get-Variable -Scope local
    

    要获取全局范围内的所有变量,请键入:

    Get-Variable -Scope global
    

    当引用变量、别名或函数时,PowerShell 会搜索当前范围。如果未找到该项目,则会搜索父范围。这种搜索一直重复到全局范围。如果变量在父作用域中是私有的,则搜索将继续通过作用域链。示例 4 显示了私有变量在范围搜索中的效果。

    PowerShell 范围名称

    PowerShell 定义了某些范围的名称,以便更轻松地访问该范围。 PowerShell 定义了以下命名范围:

    • 全局:PowerShell 启动或创建新会话或运行空间时生效的范围。 PowerShell 启动时出现的变量和函数(例如自动变量和首选项变量)是在全局范围内创建的。 PowerShell 配置文件中的变量、别名和函数也是在全局范围内创建的。全局作用域是运行空间中的根父作用域。
    • 本地:当前范围。本地作用域可以是全局作用域或任何其他作用域。
    • 脚本:脚本文件运行时创建的范围。脚本中的命令在脚本范围内运行。对于脚本中的命令,脚本作用域是本地作用域。

    对于支持作用域的 cmdlet,可以通过描述一个作用域相对于另一个作用域的相对位置的数字来引用作用域。作用域 0 表示当前(本地)作用域,作用域 1 是当前作用域的父作用域,作用域 2 是当前作用域的祖父作用域。这种模式一直持续到到达根范围。

    范围修饰符

    变量、别名或函数名可以包含以下任一可选范围修饰符:

    • global: - 指定该名称存在于全局范围内。

    • local: - 指定该名称存在于本地范围内。当前范围始终是本地范围。

    • private: - 指定名称是私有并且仅对当前范围可见。

      笔记

      private: 不是范围。该选项可更改项目定义范围之外的可访问性。

    • script: - 指定该名称存在于脚本范围内。 脚本范围是最近的祖先脚本文件的范围或全局(如果没有最近的祖先脚本文件)。

    • using: - 用于在远程会话、后台作业或线程作业中运行时访问另一个作用域中定义的变量。

    • workflow: - 指定该名称存在于工作流程中。注意:PowerShell v6 及更高版本不支持工作流。

    • <variable-namespace> - 由 PowerShell PSDrive 提供程序创建的修饰符。例如:

      Alias:

      当前作用域中定义的别名

      Env:

      当前范围内定义的环境变量

      Function:

      当前范围内定义的函数

      Variable:

      当前范围内定义的变量

    脚本的默认范围是脚本范围。函数和别名的默认作用域是本地作用域,即使它们是在脚本中定义的。

    使用范围修饰符

    要指定新变量、别名或函数的范围,请使用范围修饰符。

    变量中范围修饰符的语法是:

    $[<scope-modifier>:]<name> = <value>
    

    函数中作用域修饰符的语法是:

    function [<scope-modifier>:]<name> {<function-body>}
    

    以下命令不使用作用域修饰符,在当前或本地作用域中创建一个变量:

    $a = "one"
    

    要在 global 作用域中创建相同的变量,请使用作用域 global: 修饰符:

    $global:a = "one"
    Get-Variable a | Format-List *
    

    请注意VisibilityOptions 属性值。

    Name        : a
    Description :
    Value       : one
    Visibility  : Public
    Module      :
    ModuleName  :
    Options     : None
    Attributes  : {}
    

    将其与私有变量进行比较:

    $private:pVar = 'Private variable'
    Get-Variable pVar | Format-List *
    

    使用private范围修饰符将Options属性设置为Private

    Name        : pVar
    Description :
    Value       : Private variable
    Visibility  : Public
    Module      :
    ModuleName  :
    Options     : Private
    Attributes  : {}
    

    要在 script 作用域中创建相同的变量,请使用 script: 作用域修饰符:

    $script:a = "one"
    

    您还可以将作用域修饰符与函数一起使用。以下函数定义在全局范围内创建一个函数:

    function global:Hello {
      Write-Host "Hello, World"
    }
    

    您还可以使用作用域修饰符来引用不同作用域中的变量。以下命令首先在本地作用域中引用 $test 变量,然后在全局作用域中引用:

    $test
    $global:test
    

    using: 范围修饰符

    using 是一个特殊的作用域修饰符,用于标识远程命令中的局部变量。如果没有修饰符,PowerShell 期望在远程会话中定义远程命令中的变量。

    using 范围修饰符是在 PowerShell 3.0 中引入的。

    对于在会话外执行的任何脚本或命令,您需要使用 using 作用域修饰符来嵌入调用会话作用域中的变量值,以便会话外代码可以访问它们。以下上下文支持 using 范围修饰符:

    • 远程执行的命令,通过使用 ComputerNameHostNameSSHConnectionSession 参数(远程会话)
    • 后台作业,通过 Start-Job 启动(进程外会话)
    • 线程作业,通过 Start-ThreadJob 或 ForEach-Object -Parallel 启动(单独的线程会话)

    根据上下文,嵌入变量值要么是调用者范围内数据的独立副本,要么是对其的引用。在远程和进程外会话中,它们始终是独立的副本。

    有关详细信息,请参阅 about_Remote_Variables。

    $using: 引用仅扩展为变量的值。如果要更改调用者作用域中变量的值,则必须拥有对该变量本身的引用。您可以通过获取变量的 PSVariable 实例来创建对变量的引用。以下示例显示如何创建引用并在线程作业中进行更改。

    $Count = 1
    $refOfCount = Get-Variable Count
    
    Start-ThreadJob {
        ($using:refOfCount).Value = 2
    } | Receive-Job -Wait -AutoRemoveJob
    
    $Count
    
    2
    

    笔记

    这不是线程安全的操作。如果尝试同时更改多个线程的值,可能会导致数据损坏。您应该使用线程安全数据类型或同步原语来保护共享数据。有关详细信息,请参阅线程安全集合。

    变量值的序列化

    远程执行的命令和后台作业在进程外运行。进程外会话使用基于 XML 的序列化和反序列化来使变量值跨进程边界可用。序列化过程将对象转换为包含原始对象属性但不包含其方法的PSObject

    对于一组有限的类型,反序列化会将对象重新恢复为原始类型。再水化的对象是原始对象实例的副本。它具有类型属性和方法。对于简单类型,例如System.Version,副本是精确的。对于复杂类型,副本是不完美的。例如,重新水化的证书对象不包含私钥。

    所有其他类型的实例都是PSObject实例。 PSTypeNames 属性包含以 Deserialized 为前缀的原始类型名称,例如 Deserialized.System.Data.DataTable

    AllScope 选项

    变量和别名有一个 Option 属性,其值为 AllScope。具有 AllScope 属性的项目将成为您创建的任何子范围的一部分,尽管它们不会由父范围追溯继承。

    具有 AllScope 属性的项目在子范围中可见,并且它是该范围的一部分。在任何范围内对项的更改都会影响定义该变量的所有范围。

    管理范围

    有些 cmdlet 具有范围参数,可让您获取或设置(创建和更改)特定范围内的项目。使用以下命令查找会话中具有 Scope 参数的所有 cmdlet:

    Get-Help * -Parameter scope
    

    要查找在特定范围内可见的变量,请使用 Get-VariableScope 参数。可见变量包括全局变量、父作用域中的变量和当前作用域中的变量。

    例如,以下命令获取本地范围内可见的变量:

    Get-Variable -Scope local
    

    要在特定范围内创建变量,请使用范围修饰符或 Set-VariableScope 参数。以下命令在全局范围内创建一个变量:

    New-Variable -Scope global -Name a -Value "One"
    

    您还可以使用 New-AliasSet-AliasGet-Alias cmdlet 的 Scope 参数来指定范围。以下命令在全局范围内创建别名:

    New-Alias -Scope global -Name np -Value Notepad.exe
    

    要获取特定范围内的函数,请在该范围内使用 Get-Item cmdlet。 Get-Item cmdlet 没有Scope 参数。

    笔记

    对于使用 Scope 参数的 cmdlet,您还可以按数字引用范围。该数字描述了一个示波器与另一个示波器的相对位置。范围 0 表示当前或本地范围。范围 1 表示直接父范围。范围 2 表示父范围的父范围,依此类推。如果您创建了许多递归作用域,则编号作用域非常有用。

    使用带范围的点源表示法

    脚本和函数遵循作用域规则。您在特定范围内创建它们,并且它们仅影响该范围,除非您使用 cmdlet 参数或范围修饰符来更改该范围。

    但是,您可以使用点源表示法将脚本或函数的内容添加到当前范围。当您使用点源表示法运行脚本或函数时,它会在当前作用域中运行。脚本或函数中的任何函数、别名和变量都会添加到当前作用域。

    例如,要从脚本范围(脚本的默认设置)中的 C:\Scripts 目录运行 Sample.ps1 脚本,只需输入脚本的完整路径命令行上的文件。

    c:\scripts\sample.ps1
    

    脚本文件必须具有 .ps1 文件扩展名才能执行。路径中包含空格的文件必须用引号引起来。如果您尝试执行带引号的路径,PowerShell 将显示带引号的字符串的内容,而不是运行脚本。调用运算符 (&) 允许您执行包含文件名的字符串的内容。

    使用调用运算符运行函数或脚本在脚本范围内运行它。使用调用运算符与按名称运行脚本没有什么不同。

    & c:\scripts\sample.ps1
    

    您可以在 about_Operators 中阅读有关调用运算符的更多信息。

    要在本地范围内运行 Sample.ps1 脚本,请在脚本路径前键入一个点和一个空格 (.):

    . c:\scripts\sample.ps1
    

    现在,脚本中定义的任何函数、别名或变量都将添加到当前范围。

    无范围限制

    PowerShell 具有一些与作用域类似的选项和功能,并且可能与作用域交互。这些功能可能会与范围或范围的行为相混淆。

    会话、模块和嵌套提示是独立的环境,而不是会话中全局作用域的子作用域。

    会议

    会话是 PowerShell 运行的环境。当您在远程计算机上创建会话时,PowerShell 会与远程计算机建立持久连接。持久连接允许您将会话用于多个相关命令。

    因为会话是一个包含的环境,所以它有自己的作用域,但会话不是创建它的会话的子作用域。会话从其自己的全局范围开始。此范围独立于会话的全局范围。您可以在会话中创建子范围。例如,您可以运行脚本来在会话中创建子作用域。

    模块

    您可以使用 PowerShell 模块来共享和交付 PowerShell 工具。模块是可以包含 cmdlet、脚本、函数、变量、别名和其他有用项目的单元。除非显式导出(使用 Export-ModuleMember 或模块清单),否则无法在模块外部访问模块中的项目。因此,您可以将模块添加到会话中并使用公共项目,而不必担心其他项目可能会覆盖会话中的 cmdlet、脚本、函数和其他项目。

    默认情况下,模块被加载到运行空间的根级(全局)范围中。导入模块不会改变范围。在会话中,模块有自己的范围。考虑以下模块 C:\temp\mod1.psm1

    $a = "Hello"
    
    function foo {
        "`$a = $a"
        "`$global:a = $global:a"
    }
    

    现在我们创建一个全局变量$a,给它一个值并调用函数foo

    $a = "Goodbye"
    foo
    

    模块在模块作用域中声明变量 $a,然后函数 foo 在两个作用域中输出该变量的值。

    $a = Hello
    $global:a = Goodbye
    

    模块创建链接到导入它们的范围的并行范围容器。模块导出的项目从导入它们的范围级别开始可用。未从模块导出的项目仅在模块的范围容器内可用。模块中的函数可以访问导入它们的范围内的项目以及模块范围容器中的项目。

    如果您从Module1加载Module2,则Module2将加载到Module1的范围容器中。 Module2 的任何导出都会放置在 Module1 的当前模块范围内。如果您使用Import-Module -Scope local,则导出将放置到当前范围对象中,而不是放置在顶层。如果您在一个模块中并使用Import-Module -Scope global(或Import-Module -Global)加载另一个模块,则该模块和它的导出被加载到全局范围而不是模块的本地范围。 WindowsCompatibility 功能执行此操作以将代理模块导入全局会话状态。

    嵌套提示

    嵌套提示没有自己的范围。当您输入嵌套提示时,嵌套提示是环境的子集。但是,您仍然在本地范围内。

    脚本确实有自己的范围。如果您正在调试脚本,并且到达脚本中的断点,则您将进入脚本作用域。

    私人选项

    别名和变量有一个 Option 属性,其值为 Private。具有 Private 选项的项目可以在其创建范围内查看和更改,但无法在该范围之外查看或更改。

    例如,如果您创建一个在全局范围内具有私有选项的变量,然后运行脚本,则脚本中的 Get-Variable 命令不会显示私有变量。在此实例中使用全局作用域修饰符不会显示私有变量。

    您可以使用 New-VariableSet-VariableNew-AliasNew-AliasOption 参数code>Set-Alias cmdlet 将 Option 属性的值设置为 Private。

    能见度

    变量或别名的 Visibility 属性决定您是否可以在创建该项目的容器外部看到该项目。容器可以是模块、脚本或管理单元。为容器设计可见性的方式与为范围设计 Option 属性的 Private 值的方式相同。

    Visibility 属性采用 PublicPrivate 值。具有私有可见性的项目只能在创建它们的容器中查看和更改。如果添加或导入容器,则无法查看或更改具有私有可见性的项目。

    由于可见性是为容器设计的,因此它在一定范围内的工作方式有所不同。

    • 如果您创建的项目在全局范围内具有私有可见性,则您无法在任何范围内查看或更改该项目。
    • 如果您尝试查看或更改具有私有可见性的变量的值,PowerShell 将返回错误消息。

    您可以使用 New-VariableSet-Variable cmdlet 创建具有私有可见性的变量。

    示例

    示例 1:仅在脚本中更改变量值

    以下命令更改脚本中 $ConfirmPreference 变量的值。此更改不会影响全局范围。

    首先,要在本地范围内显示 $ConfirmPreference 变量的值,请使用以下命令:

    PS>  $ConfirmPreference
    High
    

    创建包含以下命令的 Scope.ps1 脚本:

    $ConfirmPreference = "Low"
    "The value of `$ConfirmPreference is $ConfirmPreference."
    

    运行脚本。该脚本更改 $ConfirmPreference 变量的值,然后在脚本范围内报告其值。输出应类似于以下输出:

    The value of $ConfirmPreference is Low.
    

    接下来,测试当前范围内 $ConfirmPreference 变量的当前值。

    PS>  $ConfirmPreference
    High
    

    此示例表明,对脚本作用域中变量值的更改不会影响父作用域中变量的值。

    示例2:查看不同范围内的变量值

    您可以使用作用域修饰符来查看本地作用域和父作用域中变量的值。

    首先,在全局范围内定义一个 $test 变量。

    $test = "Global"
    

    接下来,创建一个定义 $test 变量的 Sample.ps1 脚本。在脚本中,使用范围修饰符来引用 $test 变量的全局或本地版本。

    在 Sample.ps1 中:

    $test = "Local"
    "The local value of `$test is $test."
    "The global value of `$test is $global:test."
    

    运行 Sample.ps1 时,输出应类似于以下输出:

    The local value of $test is Local.
    The global value of $test is Global.
    

    脚本完成后,会话中仅定义 $test 的全局值。

    PS> $test
    Global
    

    示例 3:更改父作用域中变量的值

    除非您使用 Private 选项或其他方法保护项目,否则您可以查看和更改父作用域中变量的值。

    首先,在全局范围内定义一个 $test 变量。

    $test = "Global"
    

    接下来,创建一个定义 $test 变量的 Sample.ps1 脚本。在脚本中,使用范围修饰符来引用 $test 变量的全局或本地版本。

    在示例.ps1 中:

    $global:test = "Local"
    "The global value of `$test is $global:test."
    

    脚本完成后,$test 的全局值会发生更改。

    PS> $test
    Local
    

    示例 4:创建私有变量

    通过使用 private: 范围修饰符或通过将 Option 属性设置为 Private 创建变量,可以将变量设置为私有。私有变量只能在创建它们的范围内查看或更改。

    在此示例中,ScopeExample.ps1 脚本创建五个函数。第一个函数调用下一个函数,该函数创建一个子作用域。其中一个函数有一个私有变量,该变量只能在创建它的范围内看到。

    PS> Get-Content ScopeExample.ps1
    # Start of ScopeExample.ps1
    function funcA {
        "Setting `$funcAVar1 to 'Value set in funcA'"
        $funcAVar1 = "Value set in funcA"
        funcB
    }
    
    function funcB {
        "In funcB before set -> '$funcAVar1'"
        $private:funcAVar1 = "Locally overwrite the value - child scopes can't see me!"
        "In funcB after set  -> '$funcAVar1'"
        funcC
    }
    
    function funcC {
        "In funcC before set -> '$funcAVar1' - should be the value set in funcA"
        $funcAVar1 = "Value set in funcC - Child scopes can see this change."
        "In funcC after set  -> '$funcAVar1'"
        funcD
    }
    
    function funcD {
        "In funcD before set -> '$funcAVar1' - should be the value from funcC."
        $funcAVar1 = "Value set in funcD"
        "In funcD after set  -> '$funcAVar1'"
        '-------------------'
        ShowScopes
    }
    
    function ShowScopes {
        $funcAVar1 = "Value set in ShowScopes"
        "Scope [0] (local)  `$funcAVar1 = '$(Get-Variable funcAVar1 -Scope 0 -ValueOnly)'"
        "Scope [1] (parent) `$funcAVar1 = '$(Get-Variable funcAVar1 -Scope 1 -ValueOnly)'"
        "Scope [2] (parent) `$funcAVar1 = '$(Get-Variable funcAVar1 -Scope 2 -ValueOnly)'"
        "Scope [3] (parent) `$funcAVar1 = '$(Get-Variable funcAVar1 -Scope 3 -ValueOnly)'"
        "Scope [4] (parent) `$funcAVar1 = '$(Get-Variable funcAVar1 -Scope 4 -ValueOnly)'"
    }
    funcA
    # End of ScopeExample.ps1
    PS> .\ScopeExample.ps1
    

    输出显示每个范围内变量的值。您可以看到私有变量仅在创建它的作用域 funcB 中可见。

    Setting $funcAVar1 to 'Value set in funcA'
    In funcB before set -> 'Value set in funcA'
    In funcB after set  -> 'Locally overwrite the value - child scopes can't see me!'
    In funcC before set -> 'Value set in funcA' - should be the value set in funcA
    In funcC after set  -> 'Value set in funcC - Child scopes can see this change.'
    In funcD before set -> 'Value set in funcC - Child scopes can see this change.' - should be the value from funcC.
    In funcD after set  -> 'Value set in funcD'
    -------------------
    Scope [0] (local)  $funcAVar1 = 'Value set in ShowScopes'
    Scope [1] (parent) $funcAVar1 = 'Value set in funcD'
    Scope [2] (parent) $funcAVar1 = 'Value set in funcC - Child scopes can see this change.'
    Scope [3] (parent) $funcAVar1 = 'Locally overwrite the value - child scopes can't see me!'
    Scope [4] (parent) $funcAVar1 = 'Value set in funcA'
    

    ShowScopes 的输出所示,您可以使用 Get-Variable 并指定范围编号来访问其他范围的变量。

    示例 5:在远程命令中使用局部变量

    对于在本地会话中创建的远程命令中的变量,请使用 using 范围修饰符。 PowerShell 假定远程命令中的变量是在远程会话中创建的。

    语法是:

    $using:<VariableName>
    

    例如,以下命令在本地会话中创建一个 $Cred 变量,然后在远程命令中使用该 $Cred 变量:

    $Cred = Get-Credential
    Invoke-Command $s {Remove-Item .\Test*.ps1 -Credential $using:Cred}
    

    using 范围修饰符是在 PowerShell 3.0 中引入的。

    参见

    • about_Environment_Variables
    • about_函数
    • about_Script_Blocks
    • about_变量
    • ForEach-Object
    • Start-ThreadJob

    您需要 登录账户 后才能发表评论

    取消回复欢迎 发表评论:

    关灯