Parsing a file to create an array of lines

前端 未结 2 549
再見小時候
再見小時候 2020-12-06 22:26

This seems so incredibly simple but I am missing something. I just need to add an array to array[0], array[1], etc. I am taking a vcard file and trying to read all the lines

相关标签:
2条回答
  • 2020-12-06 22:37

    tl;dr:

    • You cannot "grow" an array by assigning to a nonexistent index; if you start with @() - an empty array - you must use += to "append" elements (arrays are fixed-size collections, so what really happens is that a new array must be allocated every time that contains the old elements followed by the new one).

    • Using += is therefore inefficient in loops, and there are two alternatives:

      • Use a .NET extensible list type to build an array-like collection more efficiently.

      • Preferably - because it is both more convenient and faster - let PowerShell create the array for you, simply by capturing the output from a foreach loop in a variable
        ($array = @(foreach (...) { ... }))

    Details below.


    Your code indeed has a problem, though the symptom it would produce differs from what your question currently states; using a simplified example:

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

    That is, @() creates an empty array, which you cannot implicitly "extend" by accessing a non-existent index.

    Using +=, as you do with your $contacts array, does work:

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

    Note the use of array-construction operator , to ensure that the RHS operand is added as a whole as a single new element; without it, multiple elements would be added, one for each element.

    However, while "extending" an array with += works, in reality you're creating a new array behind the scenes every time, because arrays are by definition fixed-size collections.

    With larger collections, this can become a performance issue, and it is better to use a list data type instead, such as [System.Collections.Generic.List[object]][1]:

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

    Note the need to enclose the array to add - as a single list element - in (...) so that the .Add() method recognizes it as a single argument.


    Taking a step back: You can let PowerShell collect the $contact sub-arrays in the overall $allcontacts array by simply capturing the output from the entire foreach command:

    $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 will end up as a regular PowerShell array, typed [object[]]. Use of the array-subexpression operator (@(...)) is only necessary if you need to ensure that $allcontacts is an array even if the *.vcf file contains only one contact definition.


    [1] A non-generic alternative is [System.Collections.ArrayList], but its downside is that its .Add() method returns a value, requiring you to suppress that value with, e.g., $null = $arrayList.Add(...) so as not to pollute PowerShell's output stream.

    0 讨论(0)
  • 2020-12-06 22:59

    This should do exactly what you want:

    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
    
    }
    
    0 讨论(0)
提交回复
热议问题