为什么 PSCustomObject 使用单个字符而不是单个字符串填充?

Why is PSCustomObject being populated with individual characters instead of a single string?

我正在尝试通过翻译我为使用 FFmpeg 转换视频而制作的旧批处理脚本来学习 PowerShell。

我认为这与手头的问题几乎无关。

这是给我带来麻烦的代码片段:

[string]$FileList              = (Get-Clipboard).Split("`n")
[int]$Counter               = 0

$List                  = @(ForEach ($i in $FileList)
{
    [PSCustomObject]
   @{
        VideoHeight = (ffprobe.exe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "$i")
        VideoDuration = (ffprobe.exe -v error -select_streams v:0 -show_entries stream=duration -of csv=s=x:p=0 "$i")
    }
    $Counter++
})

将剪贴板中的文件列表存储在 $FileList 中,用新行分隔。

$Counter中存储整数0

对于 $FileList 中的每个项目,创建一个包含项目高度和持续时间的新对象,并将 1 添加到 $Counter

看起来很简单,对吧?这里有一个问题:如果 $FileList 中只有一个高度为 1080 的文件,$List.VideoHeight[0] 将 return 仅 1 但如果 [=] 中至少有两个文件13=], $List.VideoHeight[0] 将 return 1080.

命令行输出:

Single File
$List.VideoHeight:
1080
$List.VideoHeight[0]:
1
Multiple Files
$List.VideoHeight:
1080
1080
720
$List.VideoHeight[0]:
1080

知道这里发生了什么吗?我卡住了。

使用 $List[0].VideoHeight,而不是 $List.VideoHeight[0]

毕竟,从概念的角度来看,您想要获取第一个列表项 ($List[0]) 的 .VideoHeight 值,而不是整个列表的视频高度值的第一个元素 ( $List.VideoHeight).[1]


它与 $List 中的 多个 项一起工作的原因是 PowerShell 的 member-access enumeration 然后 returns 一个 数组.VideoHeight 属性 值,其中使用 [0] 的索引按预期工作。

$List 中的 单个 项目无法按预期工作的原因是只有 标量 (single) .VideoHeight 属性 返回值,并且该标量的类型为 [string]。索引到单个字符串 returns 字符串中的 个字符 ,这就是您所看到的。

简单演示:

PS> ([pscustomobject] @{ VideoHeight = '1080' }).VideoHeight[0]
1  # [char] '1', the first character in string '1080'

对比

PS> ([pscustomobject] @{ VideoHeight = '1080' }, 
     [pscustomobject] @{ VideoHeight = '1081' }).VideoHeight[0]
1080  # [string] '1080', the first element in array '1080', '1081'

所以有两个因素导致了意外行为:

  • PowerShell 的成员访问枚举应用与收集管道输出时相同的逻辑:如果正在枚举其成员的集合恰好只有 一个 元素,该成员 (属性) 值按原样返回 ;只有 2 个或更多元素导致 [object[]] 数组 值。

    • 成员访问枚举的这种行为有点不幸;当 属性 值本身就是数组时,这种行为可能会更令人惊讶:结果数组然后 连接 而不是成为输出数组中的单个子数组 - 请参阅 this GitHub issue.
  • .NET System.String 类型 ([string]) 的标准行为,它允许直接索引构成字符串的字符(例如 "foo"[0]产生 [char] 'f').

    • 严格来说,就是任何语言使用实现 [...] 索引器语法的类型,这是一种语法糖;底层类型只有一个 参数化的 属性 命名为 Chars,C# 和 PowerShell 都可以通过 [...].

      更方便地公开它
    • 然而,在 [string] 的情况下,不幸的是,这与 PowerShell 对集合和标量的统一处理冲突,其中 通常 $scalar[0]$scalar 相同 - 请参阅 了解背景信息。


[1] If 收集所有 $List .VideoHeight 属性 值 的值consistently 返回一个数组(为什么不在第二部分中解释),这两个语句在 功能上 等价,尽管 $List[0].VideoHeight 仍然更可取为了效率(不需要构造 属性 值的中间数组),也是为了避免潜在的成员名称冲突,成员访问枚举 本质上包含 - 参见 this GitHub issue.