英文:
How to get all piped in objects at once when using Begin/Process/End blocks?
问题
这是您提供的功能代码的翻译:
我在一个我已经着手学习的学习项目上卡住了,这里的目标是让我完全了解“Begin”、“Process”和“End”块的概念。
我的项目包括一个函数,允许用户打开一个具有特定配置文件的Firefox窗口,并可选择打开一个或多个网页,这些网页通过`.url`文件保存在`C:`驱动器上。以下是我目前的代码:
函数 Ask-Web{
[CmdletBinding()]
参数(
[参数(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', 'hanna')]
[数组]$FirefoxProfile = 'Andy',
[参数(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[数组]$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
}
[数组]$Pages = If($Pages -eq "SelectWithFZF"){Get-ChildItem -Path "C:\temp\Ask Pages\" -Filter *.url*| FZF}Else{$FavouritePages."$Pages"} ;FZF实用程序将所有选定项存储到$Pages变量中
}
End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
}
这个功能旨在允许用户通过参数完成选择页面,通过管道提供网页或使用FZF实用程序从文件夹C:\temp\Ask Pages\
进行选择。
在所有情况下,用户应提供一个或多个页面/网址:
Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange ; 打开一个Firefox窗口,使用配置文件"Andy",以及页面DIYhome、DIYreddit和DIYexchange
Ask-Web -FirefoxProfile hanna -Pages DIYhome ; 打开一个Firefox窗口,使用配置文件"Hanna",以及页面DIYhome
Ask-Web -FirefoxProfile andrew -Pages SelectWithFZF ; 打开一个Firefox窗口,使用配置文件"Andrew",以及使用FZF选择的所有页面
Get-ChildItem .\Diy -filter .url | Ask-Web -FirefoxProfile hanna ; 打开一个Firefox窗口,使用配置文件"Hanna",并使用管道提供的所有页面
我卡住的地方是让PowerShell一次性给我所有的管道输入,而不是一个接一个或最后一个管道项,这是上述函数给我的结果。
如果我使用 Process{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
,那么PowerShell将为$Pages
中的每个项目打开一个Firefox窗口,但使用End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
时只会打开$Pages
中的最后一个项目。
我尝试过:
函数 Ask-Web{
[CmdletBinding()]
参数(
[参数(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', 'hanna')]
[数组]$FirefoxProfile = 'Andy',
[参数(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[数组]$Pages
)
Process{[数组]$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
块,而是使用以下方式:
函数 Ask-Web{
[CmdletBinding()]
参数(
[参数(Position = 1)]
[ArgumentCompletions('andy','andrew','halley', 'hanna')]
[数组]$FirefoxProfile = 'Andy',
[参数(ValueFromPipeline, Position = 2)]
[ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
[数组]$Pages
)
$input
}
我得到一致的结果,所有数组$Pages
的元素都一次性传递,不管用户如何提供它们。如何在Begin
、Process
和End
块中实现相同的效果?非常感谢您的帮助!
英文:
I am stuck on a learning project I have set out on, the objective here was meant for me to learn this whole the Begin
, Process
and End
blocks concept
My project consists of a function, that will let the user open a single Firefox window with a specific profile and optionally open one or more web pages via .url
files saved on the C:
drive. This is what I have so far:
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}
}
The function is supposed to let the user select pages via argument completions, pre provide a web page from the pipeline or make a selection from folder C:\temp\Ask Pages\
using the utility FZF.
In all instances, the user is expected to provide one or more pages/urls:
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
Where I seem to be stuck is getting PowerShell to give me all the pipeline input at once, rather than one at a time or the last pipeline item, which is what I get with the above function.
If I go with Process{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
then PowerShell will open a Firefox window for every item there is in $Pages
, yet with End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}
only the last item in $Pages
gets opened.
I tried:
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}
}
With Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange
, in the End
block I get :
DIYhome
DIYreddit
DIYexchange
But with 'DIYhome','DIYreddit','DIYexchange'|Ask-Web -FirefoxProfile andy
in the End
block I get :
DIYexchange
DIYexchange
If I forego all the Begin
, Process
and End
blocks with:
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
}
I get consistent results, all the elements of array $Pages
are handed at once, regardless of how the user provided them. How can I do the same but with the Begin
, Process
and End
blocks?
Any help would be greatly appreciated!
答案1
得分: 2
以下是您要的内容的中文翻译:
<!-- language-all: sh -->
有两个选项,建议的选项是使用 `List<T>` 收集通过管道传递的所有输入,这是在函数的 `process` 块中完成的。然后,在收集了所有管道输入后,您可以在 `end` 块中启动 Firefox。
使用 `List<T>` 收集管道输入的主要原因是,与 `System.Array` (`@()` 和 `+=`) 不同,数组是[固定大小](https://learn.microsoft.com/en-us/dotnet/api/system.array.system-collections-ilist-add?view=net-7.0#remarks),每次添加新元素时,PowerShell 必须重新创建它,这使其非常低效。有关更多详细信息,请参见 [此答案](https://stackoverflow.com/questions/60708578/why-should-i-avoid-using-the-increase-assignment-operator-to-create-a-colle) 和 [PowerShell 脚本性能考虑 - 数组添加](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations?view=powershell-7.3#array-addition)。
```shell
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) {
# 在此处使用 `$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
}
}
# 选项 1:
Write-Output (Get-ChildItem) -NoEnumerate | Test-Pipeline
# 选项 2:
, (Get-ChildItem) | Test-Pipeline
英文:
<!-- language-all: sh -->
You have two options, the recommended one is to collect all input passed through the pipeline with a List<T>
, this is done in the process
block of your function. Then after all pipeline input has been collected you can start Firefox in your end
block.
Main reason to use List<T>
to collect pipeline input as opposed to a System.Array
(@()
and +=
) is because arrays are of a fixed size and PowerShell has to recreate it each time we add a new element to it which makes it very inefficient. See this answer and PowerShell scripting performance considerations - Array addition for more details on this.
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
The List<T>
in this case is necessary because PowerShell will pass one object at a time through the pipeline, this is what's known as One-at-a-time processing. Using $input
is not a recommended option in this case because 1. your function is an advanced one and the use of this automatic variable is not recommended in advanced functions. 2. Using $input
would mean that your function only works from pipeline only unless you work around it.
The second option, and the not recommended one, is to pass the result of Get-ChildItem
through the pipeline without enumerating it. For this you can use Write-Output -NoEnumerate
or the comma operator ,
:
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
答案2
得分: 1
你的问题是在$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
}
}
英文:
Your problem is that you have a scoping problem with your $Pages
variable.
When passing via the pipeline, the $Pages
variable gets reset to the latest value of the enumeration. This is why you are only getting the last value twice. Basically the Process block sees a new $Pages
every time around, so on the last round, you are adding the latest $input
to it.
I think the simplest solution is to introduce a new variable that you then use in the End block:
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 }
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论