Переменная $ _, используемая в функции из модуля, пуста (PowerShell) - PullRequest
13 голосов
/ 07 июня 2011

Один вопрос для вас здесь;)

У меня есть эта функция:

function Set-DbFile {
    param(
        [Parameter(ValueFromPipeline=$true)]
        [System.IO.FileInfo[]]
        $InputObject,
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [scriptblock]
        $Properties
    )
    process {
        $InputObject | % { 
            Write-Host `nInside. Storing $_.Name
            $props = & $Properties
            Write-Host '  properties for the file are: ' -nonew
            write-Host ($props.GetEnumerator()| %{"{0}-{1}" -f $_.key,$_.Value})
        }
    }
}

Посмотрите на $Properties.Он должен оцениваться для каждого файла, а затем файл и свойства должны обрабатываться далее.

Пример того, как его использовать, может быть:

Get-ChildItem c:\windows |
    ? { !$_.PsIsContainer } |
    Set-DbFile -prop { 
        Write-Host Creating properties for $_.FullName
        @{Name=$_.Name } # any other properties based on the file
    }

Когда я копирую и вставляю функцию Set-dbFile в командной строке и запустите пример кода, все в порядке.

Однако, когда я сохраняю функцию в модуле, импортирую ее и запускаю пример, переменная $_ пуста.Кто-нибудь знает почему?И как это решить?(также приветствуются другие решения)


Результаты для функции, определенной в сценарии / набранной в командной строке:

Inside. Storing adsvw.ini
Creating properties for C:\windows\adsvw.ini
  properties for the file are: Name-adsvw.ini

Inside. Storing ARJ.PIF
Creating properties for C:\windows\ARJ.PIF
  properties for the file are: Name-ARJ.PIF
....

Результаты для функции, определенной в модуле:

Inside. Storing adsvw.ini
Creating properties for
  properties for the file are: Name-

Inside. Storing ARJ.PIF
Creating properties for
  properties for the file are: Name- 
....

Ответы [ 3 ]

6 голосов
/ 23 июня 2011

Проблема здесь в иерархии областей действия. Если вы определите две функции, такие как ...

function F1{
    $test="Hello"
    F2
}
function F2{
    $test
}

Тогда F2 унаследует переменную область видимости F1, поскольку она вызывается из области видимости F1. Если вы определяете функцию F2 в модуле и экспортируете функцию, переменная $ test недоступна, поскольку модуль имеет свое собственное дерево областей действия. См. Спецификация языка Powershell (Раздел 3.5.6):

В вашем случае текущая переменная узла определена в локальной области видимости, и, следовательно, она не сохранится в области видимости модуля, поскольку находится в другом дереве с другим корнем области (кроме глобальных переменных).

Чтобы процитировать текст метода GetNewClosure () в Спецификация языка Powershell (Раздел 4.3.7):

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

... следовательно, GetNewClosure () работает, так как устраняет локальную область видимости / разделение модуля. Надеюсь, это поможет.

5 голосов
/ 18 июня 2011

Похоже, GetNewClosure() такая же хорошая работа, как и любая другая, но она меняет способ, которым блок скрипта видит эти переменные. Передача $_ в блок сценария в качестве аргумента тоже работает.

Это не имеет ничего общего с обычными проблемами области видимости (например, глобальные или локальные), но сначала это выглядит так. Вот мое очень упрощенное воспроизведение и следующее объяснение:

script.ps1 для обычного точечного источника:

function test-script([scriptblock]$myscript){
    $message = "inside"
    &{write-host "`$message from $message"}    
    &$myscript
}

Module\MyTest\MyTest.psm1 для импорта:

function test-module([scriptblock]$myscript){
    $message = "inside"
    &{write-host "`$message from $message"}    
    &$myscript
}

function test-module-with-closure([scriptblock]$myscript){
    $message = "inside"
    &{write-host "`$message from $message"}    
    &$myscript.getnewclosure()
}

Звонки и вывод:

» . .\script.ps1

» import-module mytest

» $message = "outside"

» $block = {write-host "`$message from $message (inside?)"}

» test-script $block
$message from inside
$message from inside (inside?)

» test-module $block
$message from inside
$message from outside (inside?)

» test-module-with-closure $block
$message from inside
$message from inside (inside?)

Итак, я начал охотиться, поскольку это пробудило мое любопытство, и я нашел несколько интересных вещей.

Этот раздел вопросов и ответов , который также содержит ссылку на этот отчет об ошибках - почти такая же тема, как и некоторые другие статьи в блогах, с которыми я сталкивался. Но хотя об этом и сообщалось как об ошибке, я не согласен.

На странице about_Scopes это сказано (w:

...

Restricting Without Scope

  A few Windows PowerShell concepts are similar to scope or interact with 
  scope. These concepts may be confused with scope or the behavior of scope.

  Sessions, modules, and nested prompts are self-contained environments,
  but they are not child scopes of the global scope in the session.

  ...

  Modules:
    ...

    The privacy of a module behaves like a scope, but adding a module
    to a session does not change the scope. And, the module does not have
    its own scope, although the scripts in the module, like all Windows
    PowerShell scripts, do have their own scope. 

Теперь я понимаю поведение, но это было выше и еще несколько экспериментов, которые привели меня к нему:

  • Если мы изменим $message в блоке скриптов на $local:message, тогда во всех 3 тестах будет пустое пространство, потому что $message не определено в локальной области блока скрипта.
  • Если мы используем $global:message, все 3 теста печатают outside.
  • Если мы используем $script:message, первые 2 теста печатают outside, а последние печатают inside.

Тогда я тоже прочитал это в about_Scopes:

Numbered Scopes:
    You can refer to scopes by name or by a number that
    describes the relative position of one scope to another.
    Scope 0 represents the current, or local, scope. Scope 1
    indicates the immediate parent scope. Scope 2 indicates the
    parent of the parent scope, and so on. Numbered scopes
    are useful if you have created many recursive
    scopes.
  • Если мы используем $((get-variable -name message -scope 1).value), чтобы попытаться получить значение из непосредственной родительской области, что произойдет? Мы все еще получаем outside, а не inside.

В этот момент мне было достаточно ясно, что у сессий и модулей есть свой собственный контекст объявления или своего рода контекст, по крайней мере для блоков скрипта. Блоки сценариев действуют как анонимные функции в среде, в которой они объявлены, пока вы не вызовете для них GetNewClosure(), после чего они интернализуют копии переменных, на которые они ссылаются, с одинаковыми именами в области, где был вызван GetNewClosure() ( сначала местные жители, вплоть до глобалов). Быстрая демонстрация:

$message = 'first message'
$sb = {write-host $message}
&$sb
#output: first message
$message = 'second message'
&$sb
#output: second message
$sb = $sb.getnewclosure()
$message = 'third message'
&$sb
#output: second message

Надеюсь, это поможет.

Приложение : Относительно дизайна.

Комментарий JasonMArcher заставил меня задуматься о проблеме дизайна, когда блок скриптов передается в модуль. В коде вашего вопроса, даже если вы используете обходной путь GetNewClosure(), вы должны знать имя переменной (переменных), где будет выполняться скрипт-блок, чтобы он работал.

С другой стороны, если вы использовали параметры в блоке сценария и передали ему $_ в качестве аргумента, блоку сценария не нужно знать имя переменной, ему нужно только знать, что аргумент определенного типа будет пройти Таким образом, ваш модуль будет использовать $props = & $Properties $_ вместо $props = & $Properties.GetNewClosure(), и ваш скрипт будет выглядеть примерно так:

{ (param [System.IO.FileInfo]$fileinfo)
    Write-Host Creating properties for $fileinfo.FullName
    @{Name=$fileinfo.Name } # any other properties based on the file
}

См. Ответ CosmosKey для уточнения.

1 голос
/ 07 июня 2011

Я полагаю, вам нужно вызвать getnewclosure () для этого блока скрипта, прежде чем запускать его.Вызываемые из файла или модуля сценария, блоки сценария оцениваются во время компиляции.Когда вы работаете с консоли, нет «времени компиляции».Он оценивается во время выполнения, поэтому он ведет себя иначе, чем когда он находится в модуле.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...