我正在尝试编写一个带有数组参数的 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 :-)
发布评论
评论(4)
使用自动变量
$input
。如果只需要管道输入,那么:
但我经常使用这种组合方法(一个接受输入作为参数或通过管道输入的函数):
Use the automatic variable
$input
.If only pipeline input is expected then:
But I often use this combined approach (a function that accepts input both as an argument or via pipeline):
这是使用 Powershell 2.0+ 的另一个示例。
此示例是在不需要参数的情况下进行的:
有一种情况,如果没有“elseif”,则会出错。如果没有为 Names 提供值,则 $Names 变量将不存在并且会出现问题。请参阅此 链接 进行解释。
如果需要的话,就不必那么复杂。
它的工作原理与预期完全一致,现在我在编写管道函数时总是引用该链接。
Here's another example using Powershell 2.0+
This example is if the parameter is not required:
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.
It works, exactly as expected and I now I always reference that link when writing my Piped Functions.
ValueFromPipeline
您应该使用管道 (ValueFromPipeline),因为 PowerShell 是专门为其设计的。
$args
首先,以下之间没有真正的区别:
我的函数 - 1,2,3,4
和my-function 1,2,3,4
(假设参数$ParamName
位于第一个位置)。关键是参数名称
$args
只是一个不幸的选择,因为$args
是自动变量,因此不应用作参数名称。几乎任何其他名称(不在 Sean M.的示例进行操作,但是您应该实现 cmdlet,假设它将从管道中间调用(请参阅:强烈鼓励开发指南)。(如果你想完全正确地做到这一点,你应该给出一个单数名称,复数参数名称应该仅在参数值总是多元素的情况下使用value.)
Middle
你的问题中假设的 cmdlet 不是一个很好的例子,因为它只关心输入并且有一个输出,因此我创建了另一个例子:
它基本上从名称列表中创建自定义对象(
$Names = "亚当", “本”,“携带”
)。当您通过参数提供“$Names”时,就会发生这种情况:(
它使用
ForEach
cmdlet 迭代$Name
参数中的所有名称。)当您通过管道提供
$Names
:请注意,输出非常相似(如果不是
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
andmy-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:
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:
(It iterates through all the names in
$Name
parameter using theForEach
cmdlet.)And this happens when you supply the
$Names
via the pipeline: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 theprocess
method and theForEach
loop only iterates ones every batch because the$Name
parameter contains an array with one single item eachprocess
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.我认为您可以通过使用 输入处理方法
BEGIN
、PROCESS
和END
块。我刚刚遇到了这个。这是我的控制台输出,只是在玩这个,您可以通过将函数的主体放在PROCESS
块中来看到它的行为方式与您期望的方式不工作
工作
I think you can achieve this by using the input processing methods
BEGIN
,PROCESS
, andEND
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 thePROCESS
block it behaves the way you would expectNot working
Working