Подтвердить что ты не робот

Переменная область видимости в PowerShell

Печальная вещь в PowerShell заключается в том, что функция и скриптовые блоки динамически ограничены.

Но есть еще одна вещь, которая меня удивила: переменные ведут себя как копирование на запись во внутренней области.

[email protected]("g")
function foo()
{
    $array += "h"
    Write-Host $array
}

& {
    $array +="s"
    Write-Host $array
}
foo

Write-Host $array

Вывод:

g s
g h
g

Что делает динамическое масштабирование немного менее болезненным. Но как мне избежать копирования-на-записи?

4b9b3361

Ответ 1

Вы можете использовать модификаторы области видимости или командлеты *-Variable.

Модификаторы области действия:

  • global используется для доступа/изменения в самой внешней области (например, в интерактивной оболочке)
  • script используется для доступа/изменения в области работающего скрипта (файл .ps1). Если сценарий не запущен, он работает как global.

(Параметр -Scope командлетов *-Variable см. в справке.)

Например. во втором примере, чтобы напрямую изменить глобальный $array:

& {
  $global:array +="s"
  Write-Host $array
}

Подробнее см. в разделе справки about_scopes.

Ответ 2

Статья об областях применения PowerShell (about_Scopes) хороша, но слишком многословна, так что это цитата из моей статьи:

В общем, области видимости PowerShell похожи на области .NET. Они:

  • Глобальный общедоступен
  • Сценарий является внутренним
  • Частный является частным
  • Локальный - текущий уровень стека
  • Пронумерованные области действия от 0..N, где каждый шаг до уровня стека (а 0 - Локальный)

Вот простой пример, который описывает использование и влияние областей:

$test = 'Global Scope'
Function Foo {
    $test = 'Function Scope'
    Write-Host $Global:test                                  # Global Scope
    Write-Host $Local:test                                   # Function Scope
    Write-Host $test                                         # Function Scope
    Write-Host (Get-Variable -Name test -ValueOnly -Scope 0) # Function Scope
    Write-Host (Get-Variable -Name test -ValueOnly -Scope 1) # Global Scope
}
Foo

Как видите, вы можете использовать синтаксис $ Global: test как только с именованными областями, $ 0: test всегда будет $ null.

Ответ 3

Не просто переменные. Когда это говорит "item", это означает переменные, функции, псевдонимы и psdrives. Все они имеют область действия.

LONG DESCRIPTION  
    Windows PowerShell protects access to variables, aliases, functions, and
    Windows PowerShell drives (PSDrives) by limiting where they can be read and
    changed. By enforcing a few simple rules for scope, Windows PowerShell
    helps to ensure that you do not inadvertently change an item that should
    not be changed.

    The following are the basic rules of scope:

        - An item you include in a scope is visible in the scope in which it
          was created and in any child scope, unless you explicitly make it
          private. You can place variables, aliases, functions, or Windows
          PowerShell drives in one or more scopes.

        - An item that you created within a scope can be changed only in the
          scope in which it was created, unless you explicitly specify a
          different scope.

Копия проблемы с записью, которую вы видите, связана с тем, как Powershell обрабатывает массивы. Добавление к этому массиву фактически уничтожает исходный массив и создает новый. Поскольку он был создан в этой области, он уничтожается, когда функция или блок script выходят и область удаляется.

Вы можете явно использовать переменные при их обновлении или использовать объекты [ref] для выполнения своих обновлений или написать свой script, чтобы вы обновляли свойство объекта или клавишу хэш-таблицы объект или хеш-таблицу в родительской области. Это не создает новый объект в локальной области, он изменяет объект в родительской области.

Ответ 4

В то время как другие сообщения дают много полезной информации, они, кажется, только спасают вас от RTFM.
Ответ, который не был упомянут, - тот, который я считаю наиболее полезным!

([ref]$var).value = 'x'

Это изменяет значение $ var независимо от того, в какой области он находится. Вам не нужно знать его область; только то, что оно на самом деле уже существует. Чтобы использовать пример OP:

[email protected]("g")
function foo()
{
    ([ref]$array).Value += "h"
    Write-Host $array
}
& {
    ([ref]$array).Value +="s"
    Write-Host $array
}
foo
Write-Host $array

Производит:

g s
g s h
g s h

Объяснение:
([ref] $ var) возвращает указатель на переменную. Поскольку это операция чтения, она разрешается в самой последней области, которая действительно создала это имя. Это также объясняет ошибку, если переменная не существует, потому что [ref] не может ничего создать, она может только возвращать ссылку на то, что уже существует.

Затем .value переносит вас в свойство, содержащее определение переменной; который вы можете установить.

У вас может возникнуть соблазн сделать что-то подобное, потому что иногда это выглядит так, как будто это работает.

([ref]$var) = "New Value"

НЕ !!!!
Случаи, когда он выглядит так, как будто он работает, являются иллюзией, потому что PowerShell делает что-то, что он делает только при некоторых очень узких обстоятельствах, таких как командная строка. Вы не можете рассчитывать на это. На самом деле это не работает в примере OP.