Reputation: 41
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 of one vcard and put them in an array and then place that array in an array so array[0] will be vcard 1, array[1] will be the next, etc.
$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=@()
}
}
Result: Unable to index into an object of type System.String.
Upvotes: 3
Views: 1137
Reputation: 437823
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.
Upvotes: 5
Reputation: 1782
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
}
Upvotes: 2