【问题标题】:Pipe complete array-objects instead of array items one at a time?管道完整的数组对象而不是一次一个数组项?
【发布时间】:2015-04-30 16:35:21
【问题描述】:

如何将一个 CmdLet 的输出作为一个完整的数组对象发送到管道中的下一个 CmdLet,而不是一次一个地发送数组中的单个项?

问题 - 一般描述
正如在about_pipelines (help pipeline) 的帮助中可以看到的,powershell 在管道中同时发送一个对象¹。所以Get-Process -Name notepad | Stop-Process 会同时向管道发送一个进程。

假设我们有一个无法以任何方式修改或更改的第 3 方 CmdLet (Do-SomeStuff)。如果传递一个字符串数组或传递一个字符串对象,Do-SomeStuff 的性能会有所不同。

Do-SomeStuff 只是一个示例,它可以替换为 ForEach-ObjectSelect-ObjectWrite-Host(或任何其他接受管道输入的 CmdLet)

Do-SomeStuff 在此示例中将同时处理数组中的单个项目。

$theArray = @("A", "B", "C")
$theArray | Do-SomeStuff

如果我们想将完整的数组作为一个对象发送给 Do-SomeStuff,可以尝试这样的操作

@($theArray) | Do-SomeStuff

但它不会产生预期的结果,因为 PowerShell “忽略”了新的单项数组。

那么,您如何“强制”$theArray 作为单个数组对象而不是当时一个内容项沿管道传递?


问题 - 实际示例
如下所示,Write-Host 的输出是不同的,如果传递一个数组,或者它当时传递了数组中的单个项目。

PS C:\> $theArray = @("A", "B", "C")
PS C:\> Write-Host $theArray
A B C
PS C:\> $theArray | foreach{Write-Host $_}
A
B
C
PS C:\> @($theArray) | foreach{Write-Host $_}
A
B
C

如何让$theArray | foreach{Write-Host $_} 产生与Write-Host $theArray 相同的输出?




脚注

  1. Powershell 中的管道处理

一个普通的字符串数组

PS C:\> @("A", "B", "C").GetType().FullName
System.Object[]


一个普通的字符串数组,通过管道传输到 Foreach-Object

PS C:\> @("A", "B", "C") | foreach{$_.GetType().FullName}
System.String
System.String
System.String

数组中的每个字符串由 ForEach-Object CmdLet 一次处理一个。


数组数组,其中“内部”数组是字符串数组。

PS C:\> @(@("A", "B", "C"), @("D", "E", "F"), @("G", "H", "I")) | foreach{$_.GetType().FullName}
System.Object[]
System.Object[]
System.Object[]

数组中的每个数组由 ForEach-Object CmdLet 一次处理一个,输入的每个子数组的内容即使是一个数组也被视为一个对象。

【问题讨论】:

  • Powershell 展开数组。在这个网站和其他地方有关于这个的讨论。展开很奇怪,但这意味着您不能@(@(@())) 并获得多个数组(大致)。
  • 请提出一个问题。删除所有其他无关的东西(这些都与您的原始问题无关)并且您的原始问题是您最少解释的问题。将输出包装在 @() 中不起作用吗?
  • 我同意@EtanReisner,这很难理解。如果您有多个问题,请分别发布。即使一些支持示例重叠,最好这样做。同样如前所述,powershell 展开数组。它还试图强制比较的右侧,这就是为什么交换对象可以给你不同的结果。
  • 你想做什么? "A", "B", "C" | Format-Custom -Expand CoreOnly 证明您正在将整个阵列推送到 Format-Custom cmdlt,那么您想要什么?
  • 澄清、阐述和具体化问题

标签: arrays powershell object syntax pipeline


【解决方案1】:

简答:使用一元数组运算符,

,$theArray | foreach{Write-Host $_}

长答案: 关于@() 运算符,您应该了解一件事:它总是将其内容解释为语句,即使内容只是一个表达式。考虑这段代码:

$a='A','B','C'
$b=@($a;)
$c=@($b;)

我在这里添加了显式的语句结束标记 ;,尽管 PowerShell 允许省略它。 $a 是三个元素的数组。 $a; 语句的结果是什么? $a 是一个集合,因此应该枚举集合,并且每个单独的项目都应该通过管道传递。所以$a; 语句的结果是三个元素写入管道。 @($a;) 看到三个元素,但不是原始数组,并从它们创建数组,所以 $b 是三个元素的数组。同样的方式$c 是相同的三个元素的数组。因此,当您编写 @($collection) 时,您会创建数组,即复制 $collection 的元素,而不是单个元素的数组。

【讨论】:

  • 谢谢,现在当你告诉我要查找什么时,这一切都在文档中TechNet: about_Operators",逗号运算符作为二元运算符,逗号创建一个数组。作为一元运算符, 逗号创建一个包含一个成员的数组。将逗号放在成员之前。 $myArray = 1,2,3 $SingleArray = ,1"
  • @NoOneSpecial 我用@() 操作员行为的详细信息更新了我的答案。
  • 那么C#的等价物是什么:object[] myArray = new object[] { new object[0] };
  • @NoOneSpecial $array=,@()
  • @PetSerAl 感谢您提供有用的回答。我相信您的长答案部分中的 @() 运算符没有什么特别之处。 () 表达式在提供的示例中展示了完全相同的行为(至少在 Powershell v5+ 中)
【解决方案2】:

逗号字符使数据成为一个数组。为了使管道将您的数组作为数组处理,而不是单独对每个数组元素进行操作,您可能还需要用括号将数据包装起来。

如果您需要评估数组中多个项目的状态,这很有用。

使用以下函数

function funTest {
    param (
        [parameter(Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [alias ("Target")]
        [array]$Targets 
        ) # end param
    begin {}
    process {
        $RandomSeed = $( 1..1000 | Get-Random )
        foreach ($Target in $Targets) {
            Write-Host "$RandomSeed - $Target"
            } # next target
        } # end process
    end {}
    } # end function 

考虑以下示例:

仅将数组括在括号中并不能保证函数将在一次进程调用中处理值数组。在这个例子中,我们看到了数组中每个元素的随机数变化。

PS C:\> @(1,2,3,4,5) | funTest
153 - 1
87 - 2
96 - 3
96 - 4
986 - 5

仅添加前导逗号,也不保证函数将在一次进程调用中处理值数组。在这个例子中,我们看到了数组中每个元素的随机数变化。

PS C:\> , 1,2,3,4,5 | funTest
1000 - 1
84 - 2
813 - 3
156 - 4
928 - 5

使用前导逗号和括号中的值数组,我们可以看到随机数保持不变,因为正在利用函数的 foreach 命令。

PS C:\> , @( 1,2,3,4,5) | funTest
883 - 1
883 - 2
883 - 3
883 - 4
883 - 5

【讨论】:

    【解决方案3】:

    如果你不介意你的过程是一个函数,那么有一个老派的解决方案。

    示例:您希望将数组复制到剪贴板,以便您可以在另一个系统上再次构建它,而无需任何 PSRemoting 连接。因此,您希望将包含“A”、“B”和“C”的数组转换为字符串: @("A","B","C") ...而不是文字数组。

    所以你构建了这个(由于其他原因这不是最佳的,但请继续关注主题):

    # Serialize-List
    
    param 
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [string[]]$list
    )
        $output = "@(";
    
        foreach ($element in $list)
        {
            $output += "`"$element`","
        }
    
        $output = $output.Substring(0, $output.Length - 1)
        $output += ")"
        $output
    

    当您直接将数组指定为参数时它会起作用:

    Serialize-List $list
    @("A","B","C")
    

    ...但当你通过管道传递它时不会那么多:

    $list | Serialize-List
    @("C")
    

    但是用 begin、process 和 end 块重构你的函数:

    # Serialize-List
    
    param 
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [string[]]$list
    )
    
    begin
    {
        $output = "@(";
    }
    
    process
    {
        foreach ($element in $list)
        {
            $output += "`"$element`","
        }
    }
    
    end
    {
        $output = $output.Substring(0, $output.Length - 1)
        $output += ")"
        $output
    }
    

    ...您可以通过两种方式获得所需的输出。

    Serialize-List $list
    @("A","B","C")
    
    $list | Serialize-List
    @("A","B","C")
    

    【讨论】:

      【解决方案4】:

      更多的“correct”方式是使用Write-Output cmdlet 并指定-NoEnumerate 开关:

      Write-Output $theArray -NoEnumerate | Do-SomeStuff
      

      另外,作者指出:

      我有第二种方式,它更像是一种黑客攻击(我尽量避免这样的黑客攻击)。您可以在数组前面放置一个逗号,然后再使用管道。

      两者都可以,但使用 comma operator 将始终创建一个 additional 数组来包含原始数组,而 Write-Output -NoEnumerate 会将 original 数组写入一步到位。

      【讨论】:

      • 请详细说明为什么Write-Output 比使用一元数组运算符更正确。
      • @NoOneSpecial 查看我的编辑
      【解决方案5】:
      $ar="1","2","3"
      
      $ar | foreach { $_ }
      

      【讨论】:

      • 虽然这段代码 sn-p 可以解决问题,但它没有解释为什么或如何回答这个问题。请include an explanation for your code,因为这确实有助于提高您的帖子质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。您可以使用edit 按钮改进此答案以获得更多选票和声誉!
      • 这通过什么方式解决问题? $ar | foreach { $_ }$ar 几乎相同。它不会像Write-Host $ar 那样将数组作为一个对象处理。
      猜你喜欢
      • 2023-04-02
      • 1970-01-01
      • 2022-10-24
      • 1970-01-01
      • 2015-04-14
      • 1970-01-01
      • 2022-12-07
      • 2020-05-06
      • 1970-01-01
      相关资源
      最近更新 更多