【问题标题】:Why Powershell Array of Array display different content if not assign to a variable如果未分配给变量,为什么Powershell Array of Array会显示不同的内容
【发布时间】:2019-01-19 17:12:15
【问题描述】:

如果 cmdlet 返回数组数组,例如:

function test() {
    $results = New-Object System.Collections.ArrayList
    $array = @()
    for ($idx = 0; $idx -lt 3; $idx++) {
         $obj = New-Object PSObject -Property @{
            "key1" = "value1";
         }
        $array += @($obj)
    }
    [Void] $results.add($array)
    return ,$results.TOArray()
}

那么当返回值被赋值时,输出就不一样了。

如果直接运行test会显示:

test


Length         : 3
LongLength     : 3
Rank           : 1
SyncRoot       : {@{key1=value1}, @{key1=value1}, @{key1=value1}}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 3

分配给变量时:

$result = test
$result

key1  
----  
value1
value1
value1

如果 cmdlet 返回一个一级数组,test$(test) 的输出是相同的。

function test() {
    $array = New-Object System.Collections.ArrayList
    for ($idx = 0; $idx -lt 3; $idx++) {
         $obj = New-Object PSObject -Property @{
            "key1" = "value1";
         }
        $array += @($obj)
    }
    return ,$array
} 

test 的输出:

key1  
----  
value1
value1
value1

【问题讨论】:

  • 去掉return ,$results.ToArray()test 中的逗号将返回与将其分配给变量时完全相同的内容。逗号将您使用 $results.ToArray() 获得的数组添加到具有一个元素的新数组中。 Powershell 在分配给变量时会解开(展平)它。刚刚运行 test 所看到的输出是使用逗号创建的新数组的数组属性。
  • 这是表达式与命令的关系:(test) 是表达式,但 test 是命令。
  • @Theo 但如果删除逗号,则结果是一个 psobject 数组,而不是一个数组数组。这不是预期的结果。

标签: powershell


【解决方案1】:

PetSerAl 在简洁的评论中提供了关键指针:

输出渲染的不同归结为:

  • statement test 是一个命令(对函数、cmdlet 或外部程序的调用)
  • $result(之前捕获 test 的输出)是一个表达式(仅涉及变量引用、PowerShell 的运算符和 .NET 方法调用,在管道之外 - 尽管它可能包含 嵌套 命令)。

通过从test 函数(函数是命令的一种形式)输出, $results.ToArray(),您正在使用数组构造运算符, 包装$results.ToArray()(导致数组数组)在一个辅助的、临时的单元素数组中,这是一种确保集合作为单个对象而不是它的传递的常用技术枚举的元素

也就是说,辅助。包装数组是:

  • 总是丢失输出到管道,由于管道的自动展开(展开)行为,

  • 但它确保包装的数组被管道中的下一个命令视为单个对象。。 p>

在您的函数中,, $results.ToArray() 在概念上更清晰但更详细的等价物是Write-Output -NoEnumerate $results.ToArray();也就是说,PowerShell 通常将隐式输出设为显式,并请求抑制 枚举 输出集合的默认行为。

鉴于管道中没有其他命令,tests 输出会隐式打印到 屏幕。 在本例中,将数组数组打印为单个对象会产生您所看到的属性列表输出格式。

相比之下,$result,由于是一个表达式,是隐式枚举的。也就是说,从test 捕获的数组数组 - 没有 aux。包装数组! - 一次一个元素发送到输出格式系统,然后这些元素呈现得更有意义。


提供一个更简单的示例

假设您的函数 test 使用 return , , (1..3) 输出一个容器数组,其中包含一个最终包装在 aux 中的 3 元素数组。单元素数组(顺便说一句:PowerShell 中的 return 只是 exiting 函数或脚本块的语法糖,它与 输出 没有直接关系) .

执行test函数就相当于直接执行下面的表达式

, , (1..3)

即外,辅助。由于隐式枚举,数组再次被丢弃,, (1..3) 被呈现为单个对象,导致属性列表格式:

Length         : 3
LongLength     : 3
Rank           : 1
SyncRoot       : {1, 2, 3}
IsReadOnly     : False
IsFixedSize    : True
IsSynchronized : False
Count          : 3

相比之下,执行$result(在运行$result = test之后)就相当于:

, (1..3)

即外,辅助。数组在$result = test 期间丢失,容器数组现在也被隐式枚举,并且(1..3) 作为单个对象呈现更有意义(您无法从视觉上区分它与将1..3 直接发送到管道,即元素按元素):

1
2
3

如何格式化数组以供显示

当命令或表达式既没有在变量中捕获,也没有发送到管道到另一个命令,也没有重定向(使用>>>),它会隐式打印到屏幕(主机),使用 PowerShell 的默认输出-格式化系统。

你可以想一个命令,比如:

test

相当于[1]

test | Out-Host

Out-Host 根据第一个输入对象自动选择一个Format-* cmdlet 用于呈现适合手头输入的内容: 如果该对象具有 4 个或更少的属性,则选择 Format-Table;否则为Format-List

但是,如果第一个输入对象是一个集合(实现IEnumerable),那么格式化 cmdlet 的选择所基于的是该集合的 第一个元素(与作为一个整体的集合类型相反),并且集合的元素使用该 cmdlet 单独格式化。

在您的$result 变量获得输出的情况下,输入数组的第一个元素是一个[pscustomobject] 实例(使用New-Object PSObject 创建),具有1 个属性key1;因此,Format-Table 被选中,构成数组的 [pscustomobject]` 实例以表格格式显示。

相比之下,在您调用test 的情况下,输入数组的第一个元素是另一个数组,它本身没有进一步枚举。 Get-Member -InputObject (1,2) -Type Property 揭示了一个数组有 8 个属性(CountIsFixedSizeIsReadOnlyIsSynchronizedLengthLongLengthRankSyncRoot),这就是为什么 SyncRoot3764被选中,将每个属性作为名称/值对单独列出。

当然,您可以选择明确地使用格式化 cmdlet,PetSerAl 指出格式化 cmdlet 支持 -Expand 参数,这使您可以控制作为集合的输入对象的方式格式化:您可以要求枚举集合,即打印其元素-Expand EnumOnly,这是默认设置),仅显示集合自身的属性,而不打印其元素(@987654375 @),或两者兼有 (-Expand Both)。

但是请注意,您不能通过-Expand 请求其他 级别的枚举,因此您的test 输出不能直接格式化以显示嵌套数组的各个元素。 但是,通过管道传递到 Write-Output 来实现这一点很简单,它执行单独渲染元素所需的额外枚举级别:

test | Write-Output

[1] 更准确地说,正如 PetSerAl 指出的那样,它是:. { test } 2>&1 | Out-Default,使用户能够覆盖 Out-Default cmdlet 以进行自定义格式。

【讨论】:

    猜你喜欢
    • 2020-05-07
    • 1970-01-01
    • 2018-06-29
    • 2017-04-03
    • 1970-01-01
    • 2015-12-21
    • 2019-03-10
    • 1970-01-01
    • 2014-01-25
    相关资源
    最近更新 更多