如何拥有从 Powershell 中的 args 或管道获取输入的数组参数?

发布于 2024-12-05 02:57:55 字数 1458 浏览 1 评论 0 原文

我正在尝试编写一个带有数组参数的 Powershell 函数。我希望用数组作为参数或作为管道输入来调用它。因此,调用看起来像这样:

my-function -arg 1,2,3,4
my-function 1,2,3,4
1,2,3,4 | my-function

获得前两个很容易:

function my-function {
    param([string[]]$arg)
    $arg
}

但是对于管道输入来说,这就更难了。通过使用 ValueFromPipeline,可以轻松地在流程块中一次获取一个参数,但这意味着 $args 变量是具有管道输入的单个值,但如果使用 -args 则为数组。我可以在 END 块中使用 $input,但这根本无法获得 -args 输入,并且在 END 块中使用 $args 只能从管道中获取最终项目。

我想我可以通过使用 begin/process/end 块从管道中显式收集参数值来做到这一点,如下所示:

function my-function {
    param([Parameter(ValueFromPipeline=$true)][string[]]$args)

    begin {
        $a = @()
    }
    process {
        $a += $args
    }

    end {
        # Process array here
        $a -join ':'
    }
}

但这看起来非常混乱。对我来说,这似乎也是一个相对常见的要求,所以我希望它很容易实现。有没有我错过的更简单的方法?或者如果没有,有没有办法将参数处理封装到子函数中,这样我就不必将所有这些都包含在我想要这样工作的每个函数中?

我的具体要求是我正在编写以 SQL 命令作为输入的脚本。因为 SQL 可能很冗长,所以我希望允许在命令中进行管道传输(可能由另一个命令生成,或者从文件上的 get-contents 生成),但也允许使用参数形式,以实现快速 SELECT 语句。所以我从管道中获取一系列字符串,或者作为参数。如果我得到一个数组,我只想用“`n”将其连接起来以形成单个字符串 - 逐行处理是不合适的。

我想另一个问题是,我的脚本是否有更好的设计,可以使多行输入像这样更干净?


谢谢 - 诀窍是不要使用 ValueFromPipeline 然后...

我在让事情按照我想要的方式工作时遇到这么多麻烦的原因是在我的测试脚本中,我使用 $args 作为我的参数变量的名称,忘记了它是一个自动变量。所以事情的进展非常奇怪......

PS> 1,2,3,4 | ./args

PS> get-content args.ps1
param([string[]]$args)

if ($null -eq $args) { $args = @($input) }
$args -join ':'

Doh :-)

I'm trying to write a Powershell function that takes an array argument. I want it to be called with the array either as an argument, or as pipeline input. So, calling looks something like this:

my-function -arg 1,2,3,4
my-function 1,2,3,4
1,2,3,4 | my-function

It's easy enough to get the first two:

function my-function {
    param([string[]]$arg)
    $arg
}

For pipeline input, though, it's harder. It's easy to get the arguments one at a time in the process block, by using ValueFromPipeline, but that means that the $args variable is a single value with pipeline input, but an array if -args is used. I can use $input in the END block, but that doesn't get -args input at all, and using $args in an END block only gets the final item from a pipeline.

I suppose that I can do this by explicitly collecting the argument values from the pipeline using begin/process/end blocks, as follows:

function my-function {
    param([Parameter(ValueFromPipeline=$true)][string[]]$args)

    begin {
        $a = @()
    }
    process {
        $a += $args
    }

    end {
        # Process array here
        $a -join ':'
    }
}

But that seems very messy. It also seems like a relatively common requirement to me, so I was expecting it to be easy to implement. Is there an easier way that I have missed? Or if not, is there a way to encapsulate the argument handling into a sub-function, so that I don't have to include all that in every function I want to work like this?

My concrete requirement is that I'm writing scripts that take SQL commands as input. Because SQL can be verbose, I want to allow for the possibility of piping in the command (maybe generated by another command, or from get-contents on a file) but also allow for an argument form, for a quick SELECT statement. So I get a series of strings from the pipeline, or as a parameter. If I get an array, I just want to join it with "`n" to make a single string - line by line processing is not appropriate.

I guess another question would be, is there a better design for my script that makes getting multi-line input like this cleaner?


Thanks - the trick is NOT to use ValueFromPipeline then...

The reason I was having so much trouble getting things to work the way I wanted was that in my test scripts, I was using $args as the name of my argument variable, forgetting that it is an automatic variable. So things were working very oddly...

PS> 1,2,3,4 | ./args

PS> get-content args.ps1
param([string[]]$args)

if ($null -eq $args) { $args = @($input) }
$args -join ':'

Doh :-)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如此安好 2024-12-12 02:57:55

使用自动变量$input

如果只需要管道输入,那么:

function my-function {
    $arg = @($input)
    $arg
}

但我经常使用这种组合方法(一个接受输入作为参数或通过管道输入的函数):

function my-function {
    param([string[]]$arg)

    # if $arg is $null assume data are piped
    if ($null -eq $arg) {
        $arg = @($input)
    }

    $arg
}


# test
my-function 1,2,3,4
1,2,3,4 | my-function

Use the automatic variable $input.

If only pipeline input is expected then:

function my-function {
    $arg = @($input)
    $arg
}

But I often use this combined approach (a function that accepts input both as an argument or via pipeline):

function my-function {
    param([string[]]$arg)

    # if $arg is $null assume data are piped
    if ($null -eq $arg) {
        $arg = @($input)
    }

    $arg
}


# test
my-function 1,2,3,4
1,2,3,4 | my-function
凉城 2024-12-12 02:57:55

这是使用 Powershell 2.0+ 的另一个示例。

此示例是在不需要参数的情况下进行的:

function my-function {
  [cmdletbinding()]
  Param(
    [Parameter(ValueFromPipeline=$True)]
    [string[]]$Names
  )

  End {
    # Verify pipe by Counting input
    $list = @($input)
    $Names = if($list.Count) { $list } 
      elseif(!$Names) { @(<InsertDefaultValueHere>) } 
      else { @($Names) }

    $Names -join ':'
  }
}

有一种情况,如果没有“elseif”,则会出错。如果没有为 Names 提供值,则 $Names 变量将不存在并且会出现问题。请参阅此 链接 进行解释。

如果需要的话,就不必那么复杂。

function my-function {
  [cmdletbinding()]
  Param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$True)]
    [string[]]$Names
  )

  End {
    # Verify pipe by Counting input
    $list = @($input)
    if($list.Count) { $Names = $list } 

    $Names -join ':'
  }
}

它的工作原理与预期完全一致,现在我在编写管道函数时总是引用该链接。

Here's another example using Powershell 2.0+

This example is if the parameter is not required:

function my-function {
  [cmdletbinding()]
  Param(
    [Parameter(ValueFromPipeline=$True)]
    [string[]]$Names
  )

  End {
    # Verify pipe by Counting input
    $list = @($input)
    $Names = if($list.Count) { $list } 
      elseif(!$Names) { @(<InsertDefaultValueHere>) } 
      else { @($Names) }

    $Names -join ':'
  }
}

There's one case where it would error out without the 'elseif'. If no value was supplied for Names, then $Names variable will not exist and there'd be problems. See this link for explanation.

If it is required, then it doesn't have to be as complicated.

function my-function {
  [cmdletbinding()]
  Param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$True)]
    [string[]]$Names
  )

  End {
    # Verify pipe by Counting input
    $list = @($input)
    if($list.Count) { $Names = $list } 

    $Names -join ':'
  }
}

It works, exactly as expected and I now I always reference that link when writing my Piped Functions.

夏末的微笑 2024-12-12 02:57:55

ValueFromPipeline

应该使用管道 (ValueFromPipeline),因为 PowerShell 是专门为其设计的。

$args

首先,以下之间没有真正的区别:
我的函数 - 1,2,3,4
my-function 1,2,3,4(假设参数$ParamName位于第一个位置)。

关键是参数名称 $args 只是一个不幸的选择,因为 $args 是自动变量,因此不应用作参数名称。几乎任何其他名称(不在 Sean M.的示例进行操作,但是您应该实现 cmdlet,假设它将从管道中间调用(请参阅:强烈鼓励开发指南)。
(如果你想完全正确地做到这一点,你应该给出一个单数名称,复数参数名称应该仅在参数值总是多元素的情况下使用value.)

Middle

你的问题中假设的 cmdlet 不是一个很好的例子,因为它只关心输入并且有一个输出,因此我创建了另一个例子:

Function Create-Object {
    Param([Parameter(ValueFromPipeline=$true)][String[]]$Name)
    Begin {
        $Batch = 0
        $Index = 0
    }
    Process {
        $Batch++
        $Name | ForEach {
            $Index++
            [PSCustomObject]@{'Name' = $_; 'Index' = $Index; 'Batch' = $Batch}
        }
    }
}

它基本上从名称列表中创建自定义对象($Names = "亚当", “本”,“携带”)。
当您通过参数提供“$Names”时,就会发生这种情况:(

Create-Object $Names

Name  Index Batch
----  ----- -----
Adam      1     1
Ben       2     1
Carry     3     1

它使用 ForEach cmdlet 迭代 $Name 参数中的所有名称。)

当您通过管道提供 $Names

$Names | Create-Object

Name  Index Batch
----  ----- -----
Adam      1     1
Ben       2     2
Carry     3     3

请注意,输出非常相似(如果不是 batch 列,输出实际上是相同的),但是对象现在分 3 批创建,这意味着每个项目都在 process 方法中迭代,而 ForEach 循环仅在每个批次中迭代项目,因为 $Name 参数包含一个带有 每次处理迭代一个单个项目。

用例

假设$Names 来自慢速源(例如不同的威胁或远程数据库)。如果您使用管道处理 $Names,您的 cmdlet 可以开始处理 $Names(并将新对象传递到下一个 cmdlet),即使并非全部 < code>$Names 尚可用。与通过参数提供 $Names 相比,在 cmdlet 处理它们并将新对象传递到管道之前,需要先收集所有 $Names

ValueFromPipeline

You should use the pipeline (ValueFromPipeline) as PowerShell is specially designed for it.

$args

First of all, there is no real difference between:
my-function -<ParamName> 1,2,3,4 and
my-function 1,2,3,4 (assuming that the parameter $ParamName is at the first position).

The point is that the parameter name $args is just an unfortunate choice as $args is an automatic variable and therefore shouldn't be used for a parameter name. Almost any other name (that is not in the automatic variables list) should do as in the example from Sean M., but instead you should implement your cmdlet assuming that it will be called from the middle of a pipeline (see: Strongly Encouraged Development Guidelines).
(And if you want to do this completely right, you should give a singular name, plural parameter names should be used only in those cases where the value of the parameter is always a multiple-element value.)

Middle

The supposed cmdlet in your question is not a very good example as it only cares about the input and has a single output therefore I have created another example:

Function Create-Object {
    Param([Parameter(ValueFromPipeline=$true)][String[]]$Name)
    Begin {
        $Batch = 0
        $Index = 0
    }
    Process {
        $Batch++
        $Name | ForEach {
            $Index++
            [PSCustomObject]@{'Name' = $_; 'Index' = $Index; 'Batch' = $Batch}
        }
    }
}

It basically creates custom objects out of a list of names ($Names = "Adam", "Ben", "Carry").
This happens when you supply the '$Names` via an argument:

Create-Object $Names

Name  Index Batch
----  ----- -----
Adam      1     1
Ben       2     1
Carry     3     1

(It iterates through all the names in $Name parameter using the ForEach cmdlet.)

And this happens when you supply the $Names via the pipeline:

$Names | Create-Object

Name  Index Batch
----  ----- -----
Adam      1     1
Ben       2     2
Carry     3     3

Note that the output is quiet similar (if it wasn't for the batch column, the output is in fact the same) but the objects are now created in 3 separate batches meaning that every item is iterated at the process method and the ForEach loop only iterates ones every batch because the $Name parameter contains an array with one single item each process iteration.

Use case

Imaging that the $Names come from a slow source (e.g. a different threat, or a remote database). In the case you using the pipeline for processing the $Names your cmdlet can start processing the $Names (and pass the new objects onto the next cmdlet) even if not all $Names are available yet. In comparison to providing the $Names via an argument were all the $Names will need to be collected first before your cmdlet will process them and pass the new objects onto the pipeline.

花开浅夏 2024-12-12 02:57:55

我认为您可以通过使用 输入处理方法 BEGINPROCESSEND块。我刚刚遇到了这个。这是我的控制台输出,只是在玩这个,您可以通过将函数的主体放在 PROCESS 块中来看到它的行为方式与您期望的方式

不工作

λ ~ function test-pipe {
>>     param (
>>         # Parameter help description
>>         [Parameter(ValueFromPipeline=$true)]
>>         [String[]]
>>         $Texts
>>     )
>>     $Texts | % {Write-Host $_}
>> }
λ ~ "this", "that", "another"
this
that
another
λ ~ $s = @("this", "that", "another")
λ ~ $s
this
that
another
λ ~ $s | test-pipe
another
λ ~ test-pipe -Texts $s
this
that
another

工作

λ ~ function test-pipe {
>>     param (
>>         # Parameter help description
>>         [Parameter(ValueFromPipeline=$true)]
>>         [String[]]
>>         $Texts
>>     )
>>     BEGIN {}
>>     PROCESS {$Texts | % {Write-Host $_}}
>>     END {}
>>
>> }
λ ~ $s | test-pipe
this
that
another
λ ~

I think you can achieve this by using the input processing methods BEGIN, PROCESS, and END blocks. I just ran into this. Here is my console output just playing around with this and you can see by putting the body of the function in the PROCESS block it behaves the way you would expect

Not working

λ ~ function test-pipe {
>>     param (
>>         # Parameter help description
>>         [Parameter(ValueFromPipeline=$true)]
>>         [String[]]
>>         $Texts
>>     )
>>     $Texts | % {Write-Host $_}
>> }
λ ~ "this", "that", "another"
this
that
another
λ ~ $s = @("this", "that", "another")
λ ~ $s
this
that
another
λ ~ $s | test-pipe
another
λ ~ test-pipe -Texts $s
this
that
another

Working

λ ~ function test-pipe {
>>     param (
>>         # Parameter help description
>>         [Parameter(ValueFromPipeline=$true)]
>>         [String[]]
>>         $Texts
>>     )
>>     BEGIN {}
>>     PROCESS {$Texts | % {Write-Host $_}}
>>     END {}
>>
>> }
λ ~ $s | test-pipe
this
that
another
λ ~
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文