如何在使用Begin/Process/End块时一次获取所有输入对象?

huangapple go评论67阅读模式
英文:

How to get all piped in objects at once when using Begin/Process/End blocks?

问题

这是您提供的功能代码的翻译:

我在一个我已经着手学习的学习项目上卡住了这里的目标是让我完全了解Begin”、“ProcessEnd块的概念

我的项目包括一个函数允许用户打开一个具有特定配置文件的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

如果我不使用BeginProcessEnd块,而是使用以下方式:

函数 Ask-Web{
    [CmdletBinding()]
    参数(
        [参数(Position = 1)]
        [ArgumentCompletions('andy','andrew','halley', 'hanna')]
        [数组]$FirefoxProfile = 'Andy',
        [参数(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
        [数组]$Pages
    )
    $input
}

我得到一致的结果,所有数组$Pages的元素都一次性传递,不管用户如何提供它们。如何在BeginProcessEnd块中实现相同的效果?非常感谢您的帮助!

英文:

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&lt;T&gt;, 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&lt;T&gt; 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&lt;T&gt; 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(&#39;andy&#39;,&#39;andrew&#39;,&#39;halley&#39;, &quot;hanna&quot;)]
        [Array]$FirefoxProfile = &#39;Andy&#39;,
        [Parameter(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions(&#39;DIYhome&#39;,&#39;DIYreddit&#39;,&#39;DIYexchange&#39;,&#39;DIYall&#39;, &#39;SelectWithFZF&#39;)]
        [Array]$Pages
    )
    Process{
		write-host &quot;Process: $Pages&quot;
		[Array]$AllPages += $Pages
		}  
    End{
		
		$AllPages }
}

huangapple
  • 本文由 发表于 2023年4月11日 00:46:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/75978956.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定