Как начать задание функции, которую я только что определил?
Как начать задание функции, которую я только что определил?
function FOO { write-host "HEY" } Start-Job -ScriptBlock { FOO } |
Receive-Job
Receive-Job: The term 'FOO' is not recognized as the name of cmdlet,
function ,script file or operable program.
Что я делаю? Благодарю.
9 ответов
Как указывает @Shay, FOO
должен быть определен для работы. Еще один способ сделать это - использовать -InitializationScript
Параметр для подготовки сеанса.
Для вашего примера:
$functions = {
function FOO { write-host "HEY" }
}
Start-Job -InitializationScript $functions -ScriptBlock {FOO}|
Wait-Job| Receive-Job
Это может быть полезно, если вы хотите использовать одни и те же функции для разных заданий.
Предложение @Rynant о InitializationScript
отлично
Я думал, что назначение (скриптовых) блоков состоит в том, чтобы вы могли передавать их. Поэтому, в зависимости от того, как вы это делаете, я бы сказал:
$FOO = {write-host "HEY"}
Start-Job -ScriptBlock $FOO | wait-job |Receive-Job
Конечно, вы также можете параметризовать блоки скриптов:
$foo = {param($bar) write-host $bar}
Start-Job -ScriptBlock $foo -ArgumentList "HEY" | wait-job | receive-job
Для меня это сработало как:
Start-Job -ScriptBlock ${Function:FOO}
Улучшение ответа @Rynant:
Вы можете определить функцию как обычно в основной части вашего скрипта:
Function FOO
{
Write-Host "HEY"
}
а затем перезапустите это определение в блоке скриптов:
$export_functions = [scriptblock]::Create(@"
Function Foo { $function:FOO }
"@)
(имеет больше смысла, если у вас есть существенное тело функции), а затем передать их Start-Job
как указано выше:
Start-Job -ScriptBlock {FOO} -InitializationScript $export_functions| Wait-Job | Receive-Job
Мне нравится этот способ, так как легче отлаживать задания, выполняя их локально под отладчиком.
Функция должна быть внутри блока скрипта:
Start-Job -ScriptBlock { function FOO { write-host "HEY" } ; FOO } | Wait-Job | Receive-Job
Пока функция, переданная в параметр InitializationScript в Start-Job, невелика, ответ Rynant будет работать, но если функция велика, вы можете столкнуться с приведенной ниже ошибкой.
[localhost] Ошибка при запуске фонового процесса. Сообщается об ошибке: имя файла или расширение слишком длинное"
Лучшей альтернативой является захват определения функции и последующее использование Invoke-Expression в ScriptBlock.
function Get-Foo {
param
(
[string]$output
)
Write-Output $output
}
$getFooFunc = $(Get-Command Get-Foo).Definition
Start-Job -ScriptBlock {
Invoke-Expression "function Get-Foo {$using:getFooFunc}"
Get-Foo -output "bar"
}
Get-Job | Receive-Job
PS C:\Users\rohopkin> Get-Job | Receive-Job
bar
В существующих ответах есть хорошая информация, но позвольте мне попытаться систематизировать:
Фоновые задания PowerShell [1] выполняются во внепроцессном пространстве выполнения (скрытый дочерний процесс) и поэтому не имеют общего состояния с вызывающим объектом .
Поэтому,
function
определения , созданные вызывающим объектом во время сеанса, не видны фоновым заданиям и должны быть воссозданы в контексте задания. [2]Самый простой способ воссоздать определение функции — объединить обозначение переменной пространства имен (например, — см. этот ответ ) с
$using:scope
, как показано ниже.- К сожалению, из-за давней ошибки не работают ссылки в блоках скриптов, переданных в(а такжех)
-InitializationScript
параметр на момент написания этой статьи (Windows PowerShell, PowerShell (Core) 7.3.6) — см. выпуск GitHub № 4530.
- К сожалению, из-за давней ошибки не работают ссылки в блоках скриптов, переданных в(а такжех)
Самостоятельный пример:
function FOO { "HEY" }
Start-Job -ScriptBlock {
# Redefine function FOO in the context of this job.
$function:FOO = "$using:function:FOO"
# Now FOO can be invoked.
FOO
} | Receive-Job -Wait -AutoRemoveJob
Вышеуказанная строка выводаHEY
, как предполагалось.
Примечание:
Присвоение неявно создает функцию (по требованию) и делает присвоенное значение телом функции ; присвоенное значение может быть либо экземпляр или , т.е. текст исходного кода .
Ссылки
$function:FOO
извлекает тело ранее существовавшей функции в качестве экземпляра. Добавление области видимости () возвращает телоFOO
функция из области вызывающего абонента .Примечание:
Из-за
$using:
, это не экземпляр, а[string]
в случае из-за - удивительного - способа, которым[scriptblock]
экземпляры десериализуются при межпроцессной сериализации; такое поведение было заявлено как специальное — обсуждение см. в выпуске GitHub № 11698 .Таким образом,
"..."
вокруг избыточны для , но не для , где сериализация не используется, а воссоздание тела из строки необходимо во избежание повреждения состояния ( подробности см. в выпуске GitHub #16461).Тот факт, что позволяет
$using:function:FOO
ссылки вообще, вероятно, являются недосмотром , учитывая, что они были явно запрещены в блоках сценариев, используемых с (PowerShell v7+) — см. GitHub Issue #12378 .Следовательно, с
ForEach-Object -Parallel
требуется вспомогательная переменная, которая сначала преобразует тело функции в строку на стороне вызывающей стороны — см. этот ответ .
[1] Этот ответ применим не только к заданиям на основе дочерних процессов, созданным Start-Job
, но аналогично обычно предпочтительным заданиям на основе потоков , созданным с помощью Start-ThreadJob
и параллелизм на основе потоков, доступный в PowerShell (Core) 7+ с ForEach-Object
-Parallel
, а также для удаленного взаимодействия PowerShell через Invoke-Command
Короче говоря: это применимо к любому сценарию, в котором PowerShell выполняется вне пространства выполнения (в другом пространстве выполнения).
[2] Альтернативой является предоставление таких определений через файлы сценариев (
*.ps1
) или модули , которые задание должно будет иметь точечный источник (
.
) или импортировать.
Комментарий @Ben Power под принятым ответом меня тоже беспокоил, поэтому я погуглил, как получить определения функций, и нашел Get-Command
- хотя это получает только тело функции. Но его также можно использовать, если функция поступает откуда-то еще, например, из файла с точкой. Итак, я придумал следующее (придерживайтесь моего соглашения об именах:)), идея состоит в том, чтобы перестроить определения функций, разделенные символами новой строки:
Filter Greeting {param ([string]$Greeting) return $Greeting}
Filter FullName {param ([string]$FirstName, [string]$LastName) return $FirstName + " " + $LastName}
$ScriptText = ""
$ScriptText += "Filter Greeting {" + (Get-Command Greeting).Definition + "}`n"
$ScriptText += "Filter FullName {" + (Get-Command FullName).Definition + "}`n"
$Job = Start-Job `
-InitializationScript $([ScriptBlock]::Create($ScriptText)) `
-ScriptBlock {(Greeting -Greeting "Hello") + " " + (FullName -FirstName "PowerShell" -LastName "Programmer")}
$Result = $Job | Wait-Job | Receive-Job
$Result
$Job | Remove-Job
Немного другой взгляд. Функция - это просто блок сценария, назначенный переменной. О, это должна быть работа с потоками. Это не может быть foreach-object -parallel.
$func = { 'hi' } # or
function hi { 'hi' }; $func = $function:hi
start-threadjob { & $using:func } | receive-job -auto -wait
hi