使用PowerShell创建/填充csv文件时遇到一些麻烦。我是powershell的新手,所以我可能会遗漏一些明显的东西,所以请放轻松我。情况如下:
首先,我创建一个数组(?)作为我的表
#Create output table with headers
$output = @()
$row = New-Object System.Object
$row | Add-Member -MemberType NoteProperty -Name "Example Header 1" -Value $null
$row | Add-Member -MemberType NoteProperty -Name "Example Header 2" -Value $null
$row | Add-Member -MemberType NoteProperty -Name "Example Header 3" -Value $null
$output += $row
我正在使用它将其写入文件
$output | Export-Csv new.csv -NoTypeInformation
这似乎是一个带有我想要的标题的csv文件。如果有更好的方法,请告诉我。下一步是我遇到问题的地方。我现在需要以编程方式用数据填充表。导入现有的csv文件时,我能够像数组一样访问/修改表中的数据(即$output[rowIndex]."Header Name" = "new data"
)。
所以我尝试将数据添加到我新创建的表中。我写了$ouput[0]."Example Header 1" = "Test Data"
。这符合我的预期,并使用"测试数据"填充指定标题的列中的第一行。但是,我只能访问[0]。 $output[1]
等导致错误,因为我猜它们不存在。我再次尝试使用$output += $row
添加更多行,但它根本不起作用并导致一些奇怪的错误发生(如果我写入一行,它会写入所有行,可能是因为它的所有相同对象)
所以基本上我的问题是,如何从头开始创建一个csv文件,为它添加一些标题,然后开始写入所有(未知/可变数量的)行?我确信有更好的方法可以做到,但就像我说的那样,我对powershell很新。理想情况下,我希望能够通过索引(0,1,2等)访问行,但我对任何事情都持开放态度。
基本解决方案(改编自Martin Brandl's answer)
这基本上从一个csv文件中读取数据,并使用新的指定头文件将其插入到另一个文件中。
$csv = Import-Csv "MyCsv.csv"
$newCsv = @()
foreach($row in $csv) {
$newCsv += [PSCustomObject]@{
"New Column Header1" = $row."Original Column Header1"
"New Column Header2" = $row."Original Column Header2"
}
}
答案 0 :(得分:5)
使用解释您的症状来补充Martin Brandl's helpful answer (强调添加):
我再次尝试使用
$output += $row
添加更多行,但它根本不起作用并导致一些奇怪的错误发生(如果我写入一行,它会写入所有行,可能是因为它是完全相同的对象)。
确实,这就是发生的事情:在.NET术语中,类型(类) [pscustomobject]
是引用类型而不是值类型 - [pscustomobject].IsValueType
返回$false
。
如果您将参考类型的给定实例(对象)添加到数组 多个次,则所有这些元素都指向非常相同实例 强>
这是简短的演示。
$obj = [PSCustomObject] @{
'Example Header 1' = $null
'Example Header 2' = $null
}
$array = @()
foreach ($ndx in 1..2) {
# By working with the original $obj every time, you
# keep modifying its property values.
$obj.'Example Header 1' = $ndx
# Adding $obj to an array does NOT create a COPY of $obj
# but stores a REFERENCE directly to $obj in the array
# (similar to storing a pointer in unmanaged languages such as C++).
$array += $obj
}
# Print the array.
$array
这产生以下结果:
Example Header 1 Example Header 2
---------------- ----------------
2
2
如您所见,只有分配给.Example Header 1
的 last 值生效,因为两个数组元素都引用了同一个对象。
Martin的方法是解决此问题的最简单方法:在每次迭代中创建自定义对象的新实例 (通过哈希表) -literal语法,如问题本身所示:$array += [pscustomobject] @{ ... }
)。
如果您不想或不能在循环内从头开始重新创建实例,您有两个基本选择:
在每次循环迭代中克隆 模板对象,但是:
[pscustomobject]
不支持克隆(未实现[System.ICloneable]
),[hashtable]
,但PSv3 +([ordered] @{ ... }
,[System.Collections.Specialized.OrderedDictionary]
)中提供的 ordered-keys 变体可以不,但您需要有序变量才能按定义顺序获取输出列。PSv5 +:在每次循环迭代中定义自定义类并实例化 - 见下文。
在PSv5 +中,custom class允许优雅解决方案,执行效果比使用文字语法在循环中创建实例更好。
# Define a custom class that represents the rows of the
# output CSV.
# Note: [object] is being used here as the properties' type.
# In real life, you'd use more specific types such as [string]
# or [int].
class CsvRow {
[object] ${Example Header 1}
[object] ${Example Header 2}
}
$array = @()
foreach ($ndx in 1..2) {
# Instantiate the custom class.
$rowObj = [CsvRow]::new()
# Set the values.
$rowObj.'Example Header 1' = $ndx
# Add the instance to the array.
$array += $rowObj
}
# Print the array.
$array
两个因素决定了绩效:
在每次循环迭代中扩展数组的速度有多快:
使用$array += ...
按元素扩展数组元素非常方便,但性能成本很高,因为每次都必须创建 new array (数组是固定大小的)集合,不能直接扩展)。
对于可能无关紧要的小迭代计数,但数字越大,性能就越差,并且在某些时候这种方法变得不可行。
解决方法是使用[System.Collections.ArrayList]
实例来构建数组 - 请参阅下文。
在每次循环迭代中实例化新对象的速度有多快:
[CsvRow]::new()
用于实例化;由于涉及 cmdlet调用 ,功能等效的New-Object CsvRow
要慢得多。 自定义类解决方案的以下变体使用[System.Collections.ArrayList]
来确保可接受的性能,即使迭代次数较高:
# Define the custom class.
class CsvRow {
[object] ${Example Header 1}
[object] ${Example Header 2}
}
# Determine the iteration count.
$count = 1000
# Create an array list for fast and efficient build-up of the array.
$arrayList = [System.Collections.ArrayList]::new()
# Loop.
foreach ($ndx in 1..$count) {
# Instantiate the custom class.
$rowObj = [CsvRow]::new()
# Set the values.
$rowObj.'Example Header 1' = $ndx
# Add the instance to the array list.
# Note that .Add() produces output, which we don't want
# and suppress with $null = ...
$null = $arrayList.Add($rowObj)
}
# Print the array list.
$arrayList
答案 1 :(得分:4)
作为Mathias mentioned,您不应该首先创建仅包含标题的CSV。而是使用您想要的实际行填充CSV并 export 它:
[PSCustomObject]@{
'Example Header 1' = "a"
'Example Header 2' = "b"
'Example Header 3' = "c"
}, [PSCustomObject]@{
'Example Header 1' = "a2"
'Example Header 2' = "b2"
'Example Header 3' = "c2"
}, [PSCustomObject]@{
'Example Header 1' = "a3"
'Example Header 2' = "b4"
'Example Header 3' = "c5"
} | Export-Csv new.csv -NoTypeInformation
<强>输出:强>
"Example Header 1","Example Header 2","Example Header 3"
"a","b","c"
"a2","b2","c2"
"a3","b4","c5"