Я застрял в учебном проекте, который я начал, цель здесь заключалась в том, чтобы я изучил всю эту концепцию блоков Begin
, Process
и End
.
Мой проект состоит из функции, которая позволит пользователю открыть одно окно Firefox с определенным профилем и, при желании, открыть одну или несколько веб-страниц через .url
файлы, сохраненные на C:
диске. Это то, что у меня есть до сих пор:
Function Ask-Web{
[CmdletBinding()]
Param(
[Parameter(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', "hanna")]
[Array]$FirefoxProfile = 'Andy',
[Parameter(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[Array]$Pages
)
Begin{
$FavouritePages = [ordered]@{
DIYhome = "https://www.diyUK.com"
DIYreddit = "https://www.reddit.com/r/DIY/submit"
DIYexchange = "https://diy.stackexchange.com/questions/ask"
# DIYall Will handle this later
}
[array]$Pages = If($Pages -eq "SelectWithFZF"){Get-ChildItem -Path "C:\temp\Ask Pages\" -Filter *.url*| FZF}Else{$FavouritePages."$Pages"} ;the FZF utility will store all selected items into $Pages variable
}
End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
}
Предполагается, что функция позволяет пользователю выбирать страницы с помощью завершения аргументов, предварительно предоставляя веб-страницу из конвейера или делая выбор из папки C:\temp\Ask Pages\
с помощью утилиты FZF.
Во всех случаях ожидается, что пользователь предоставит одну или несколько страниц/URL-адресов:
Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange ; Open a singe Firefox window, with profile "Andy" and the pages DIYhome,DIYreddit and DIYexchange
Ask-Web -FirefoxProfile hanna -Pages DIYhome ; Open a singe Firefox window, with profile "Hanna" and the page DIYhome
Ask-Web -FirefoxProfile andrew -Pages SelectWithFZF ; Open a singe Firefox window, with profile "Andrew" and all the pages that were selected with FZF
Get-ChildItem .\Diy -filter *.url* | Ask-Web -FirefoxProfile hanna ; Open a singe Firefox window, with profile "Hanna" and with all the pages provided by the pipeline
Где я, кажется, застрял, так это в том, чтобы заставить PowerShell предоставить мне все входные данные конвейера сразу, а не по одному или последний элемент конвейера, что я получаю с помощью вышеуказанной функции.
Если я выберу Process{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
, то PowerShell откроет окно Firefox для каждого элемента в $Pages
, но с End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
открывается только последний элемент в $Pages
.
Я пытался:
Function Ask-Web{
[CmdletBinding()]
Param(
[Parameter(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', "hanna")]
[Array]$FirefoxProfile = 'Andy',
[Parameter(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[Array]$Pages
)
Process{[Array]$Pages += @($input)}
End{$Pages}
}
С Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange
в блоке End
я получаю:
DIYhome
DIYreddit
DIYexchange
Но с 'DIYhome','DIYreddit','DIYexchange'|Ask-Web -FirefoxProfile andy
в блоке End
я получаю:
DIYexchange
DIYexchange
Если я откажусь от всех блоков Begin
, Process
и End
с помощью:
Function Ask-Web{
[CmdletBinding()]
Param(
[Parameter(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', "hanna")]
[Array]$FirefoxProfile = 'Andy',
[Parameter(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[Array]$Pages
)
$input
}
Я получаю стабильные результаты, все элементы массива $Pages
передаются сразу, независимо от того, как пользователь их предоставил. Как мне сделать то же самое, но с блоками Begin
, Process
и End
?
Любая помощь будет принята с благодарностью!
У вас есть два варианта, рекомендуемый — собрать все входные данные, прошедшие через конвейер, с помощью List<T>
, это делается в блоке process
вашей функции. Затем, после того, как все входные данные конвейера будут собраны, вы можете запустить Firefox в своем блоке end
.
Основная причина использования List<T>
для сбора входных данных конвейера, а не System.Array
(@()
и +=
), заключается в том, что массивы имеют фиксированный размер , и PowerShell должен воссоздавать его каждый раз, когда мы добавляем к нему новый элемент, что делает его очень неэффективным. . См. этот ответ и Рекомендации по производительности сценариев PowerShell — добавление массива для получения более подробной информации об этом.
function Test-Pipeline {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline)]
[Array] $InputObject
)
begin {
$list = [System.Collections.Generic.List[object]]::new()
}
process {
$list.AddRange($InputObject)
}
end {
if ($list.Count) {
# do stuff here with `$list`
$list.ToArray()
}
}
}
Get-ChildItem | Test-Pipeline
List<T>
в этом случае необходим, потому что PowerShell будет передавать один объект за раз через конвейер, это то, что известно как обработка по одному . Использование $input в этом случае не рекомендуется, потому что 1. ваша функция является расширенной и использование этой автоматической переменной не рекомендуется в расширенных функциях. 2. Использование $input
будет означать, что ваша функция работает только из конвейера, только если вы не обойдете ее.
Второй вариант, и не рекомендуемый, — передать результат Get-ChildItem
через конвейер без его перечисления. Для этого вы можете использовать Write-Output -NoEnumerate или оператор запятой ,:
function Test-Pipeline {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline)]
[Array] $InputObject
)
end {
$InputObject
}
}
# option 1:
Write-Output (Get-ChildItem) -NoEnumerate | Test-Pipeline
# option 2:
, (Get-ChildItem) | Test-Pipeline
Ваша проблема в том, что у вас есть проблема с областью действия вашей переменной $Pages
.
При прохождении через конвейер переменная $Pages
сбрасывается до последнего значения перечисления. Вот почему вы получаете последнее значение только дважды. По сути, блок Process каждый раз видит новый $Pages
, поэтому в последнем раунде вы добавляете к нему последний $input
.
Я думаю, что самое простое решение — ввести новую переменную, которую затем использовать в блоке End:
Function Ask-Web{
[CmdletBinding()]
Param(
[Parameter(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', "hanna")]
[Array]$FirefoxProfile = 'Andy',
[Parameter(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[Array]$Pages
)
Process{
write-host "Process: $Pages"
[Array]$AllPages += $Pages
}
End{
$AllPages }
}
Tbh, ваш вариант использования может быть не лучшим примером для обучения конвейеру, но если вы действительно хотите продолжить его, вам следует инициализировать новую локальную переменную в вашем блоке
BEGIN
- например,$items = @()
, накапливайте элементы конвейера в своем блокеPROCESS
(например,$items += $_
), а затем сделайте что-то один раз со всеми элементами в вашем блокеEND
. Обратите внимание, что вам следует избегать+=
для массивов в производственном коде из соображений производительности, но, вероятно, это подойдет для вашего упражнения…