【问题标题】:Parsing a file to create an array of lines解析文件以创建行数组
【发布时间】:2019-07-05 15:28:10
【问题描述】:

这看起来非常简单,但我错过了一些东西。我只需要将数组添加到数组 [0]、数组 [1] 等。 我正在获取一个 vcard 文件并尝试读取一个 vcard 的所有行并将它们放入一个数组中,然后将该数组放入一个数组中,因此 array[0] 将是 vcard 1,array[1] 将是下一个,等等.

$c = Get-Content -Path C:\temp\Contacts_Backup.vcf
$counter=0
$contact=@()
$allcontacts=@()

Foreach ($line in $c){
    $contact += $line
    if ($line -eq 'END:VCARD'){
        $allcontacts[$counter++] = $contact
        $contact=@()
        }
}

结果: 无法索引到 System.String 类型的对象。

【问题讨论】:

  • 您通常需要在子数组前加上逗号,以将其作为数组添加到另一个数组。 ///// 但是,我怀疑您最好制作一个 VCF 内容的自定义对象并将 that 添加到您的主集合中。
  • 请您发布整个错误。所以我可以弄清楚你在哪一行得到错误

标签: powershell


【解决方案1】:

tl;dr

  • 您不能通过分配给一个不存在的索引来“增长”一个数组;如果您以 @() 开头 - 一个空数组 - 您必须使用 += 来“附加”元素(数组是固定大小的集合,所以真正发生的是 a 新的数组每次都必须分配,其中包含旧元素后跟新元素)。

  • 因此在循环中使用+= 效率很低,有两种替代方案

    • 使用 .NET 可扩展列表类型更有效地构建类似数组的集合。

    • 最好 - 因为它既方便又快捷 - PowerShell 为您创建数组,只需在变量中捕获来自 foreach 循环的输出
      ($array = @(foreach (...) { ... }))

详情如下。


您的代码确实有问题,尽管它会产生的症状与您的问题当前所陈述的不同;使用一个简化的例子:

PS> $allcontacts=@(); $allcontacts[0] = 'one', 'two'
Index was outside the bounds of the array.  # ERROR
...

也就是说,@() 创建了一个 数组,您不能通过访问不存在的 索引 来隐式地“扩展”该数组。

使用 +=,就像使用 $contacts 数组一样,确实有效:

$allcontacts=@(); $allcontacts += , ('one', 'two')

注意使用数组构造运算符, 以确保将RHS 操作数作为一个作为一个整体添加为一个单个新元素;没有它,多个元素将被添加,每个元素一个。

然而,虽然使用+=“扩展”数组是可行的,但实际上您每次都是在幕后创建一个数组,因为数组根据定义是固定大小的 集合。

对于较大的集合,这可能会成为性能问题,最好使用 list 数据类型,例如[System.Collections.Generic.List[object]][1]

$allcontacts = New-Object Collections.Generic.List[object]
$allcontacts.Add(('one', 'two'))

请注意,需要将数组作为单个列表元素添加到 (...) 中,以便 .Add() 方法将其识别为单个参数。


退一步:您可以让PowerShell通过简单地捕获整个foreach命令的输出来收集整个$allcontacts数组中的$contact子数组:

$c = Get-Content -Path C:\temp\Contacts_Backup.vcf
$contact=@()

$allcontacts = @(foreach ($line in $c){
    $contact += $line
    if ($line -eq 'END:VCARD'){
        # Output the $contact array as a *single* object,
        # using ",", the array-construction operator
        , $contact
        # Reset for the next contact.
        $contact=@()
    }
})

$allcontacts 将作为常规 PowerShell 数组结束,键入 [object[]]。 仅当您需要确保 $allcontacts 是一个数组时才需要使用数组子表达式运算符 (@(...)),即使 *.vcf 文件仅包含 一个 联系人定义。


[1] 一个非泛型的替代品是[System.Collections.ArrayList],但它的缺点是它的.Add() 方法返回一个值,要求您使用例如抑制该值, $null = $arrayList.Add(...)以免污染PowerShell的输出流。

【讨论】:

    【解决方案2】:

    这应该完全符合您的要求:

    Add-Type -AssemblyName System.Collections
    
    [System.Collections.Generic.List[object]]$allContacts = @()
    [System.Collections.Generic.List[string]]$contact = @()
    
    $filePath  = 'C:\temp\Contacts_Backup.vcf'
    $endMarker = 'END:VCARD'
    
    foreach($line in [System.IO.File]::ReadLines($filePath))
    {
            if( $line -eq $endMarker ) {
                $allContacts.Add( $contact.ToArray() )
                $contact.Clear()
            }
            else {
                $contact.Add( $line )
            }
    }
    
    # Ready. Show result.
    
    foreach( $vcf in $allContacts ) {
    
        "Contact: "
        $vcf
    
    }
    

    【讨论】:

    • 谢谢。唯一的问题是最后一行 (END:VCARD) 不包含在联系人中,因为它永远不会到达 else 子句。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 2020-01-26
    • 2019-11-03
    • 1970-01-01
    • 2013-01-25
    相关资源
    最近更新 更多