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

时间:2011-09-15 18:24:13

标签: arrays powershell pipeline

我正在尝试编写一个带有数组参数的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 blocks从管道中显式收集参数值来实现这一点,如下所示:

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: - )

4 个答案:

答案 0 :(得分:7)

使用自动变量$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

答案 1 :(得分:2)

这是使用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变量将不存在并且存在问题。有关说明,请参阅此link

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

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 ':'
  }
}

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

答案 2 :(得分:0)

ValueFromPipeline

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

的$ args

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

关键是参数名称$args只是一个不幸的选择,因为$args是一个自动变量,因此不应该用于参数名称。几乎任何其他名称(不在automatic variables列表中)应该像Sean M.中的示例那样,但是您应该实现cmdlet,假设它将从管道中间调用(见:Strongly Encouraged Development Guidelines)。
(如果你想完全正确地做这个,你应该给出一个单数名称,只有在参数值总是多元素的情况下才应该使用多个参数名称价值。)

中东

你问题中假设的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 = "Adam", "Ben", "Carry")中创建自定义对象 当您通过参数提供'$ Names`时会发生这种情况:

Create-Object $Names

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

(它使用$Name cmdlet遍历ForEach参数中的所有名称。)

当您通过管道提供$Names时会发生这种情况:

$Names | Create-Object

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

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

用例

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

答案 3 :(得分:0)

我认为您可以通过使用 input processing methods 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
λ ~