使用Powershell创建/填充csv文件

Tho*_*mas 2 csv powershell

我在使用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
Run Code Online (Sandbox Code Playgroud)

我正在使用它将其写入文件 $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的答案)

这基本上从一个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"
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ndl 6

正如Mathias 所提到的,您不应该首先创建仅包含标题的 CSV。相反,用您想要的实际行填充您的 CSV 并将其导出

[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
Run Code Online (Sandbox Code Playgroud)

输出:

"Example Header 1","Example Header 2","Example Header 3"
"a","b","c"
"a2","b2","c2"
"a3","b4","c5"
Run Code Online (Sandbox Code Playgroud)


mkl*_*nt0 6

为了补充Martin Brandl的有用答案解释您的症状(重点补充):

$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 the same instance's property values.
  $obj.'Example Header 1' = "h1-$ndx"
  $obj.'Example Header 2' = "h2-$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
}

# Output the array.
$array
Run Code Online (Sandbox Code Playgroud)

这产生以下结果:


Example Header 1 Example Header 2
---------------- ----------------
h1-2             h2-2
h1-2             h2-2
Run Code Online (Sandbox Code Playgroud)

如您所见,只有指定的最后一个.Example Header 1生效,因为两个数组元素都引用了同一个对象.


基于类的解决方案

Martin的方法是解决此问题的最简单方法:在每次迭代中创建自定义对象的新实例(通过hashtable-literal语法,如问题本身所示:) ..Example Header 2

如果您不想或不能在循环内从头开始重新创建实例,则有两个基本选择:

  • 克隆一个模板对象在每个循环迭代,但是:

    • $array += [pscustomobject] @{ ... }不支持克隆(不实现[pscustomobject] @{ ... }),
    • 虽然$array += ... 如此,PSv3 +(,)中可用的有序键变量却没有,但是您需要有序变量来按定义顺序获取输出列.[System.Collections.ArrayList][CsvRow]::new()
  • PSv5 +:定义一个自定义并在每次循环迭代中实例化它 - 见下文.


在PSv5 +中,自定义类允许优雅的解决方案,其性能也优于使用文字语法在循环中创建实例.

# 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' = "h1-$ndx"
  $rowObj.'Example Header 2' = "h2-$ndx"
  # Add the instance to the array.
  $array += $rowObj
}

# Output the array.
$array
Run Code Online (Sandbox Code Playgroud)

性能注意事项

两个因素决定了绩效:

  • 在每次循环迭代中扩展数组的速度有多快:

    • 使用元素扩展数组元素New-Object CsvRow非常方便,但是性能成本高,因为 每次都必须创建一个新数组(数组是固定大小的集合,不能直接扩展).

    • 对于可能无关紧要的小迭代计数,但数字越大,性能就越差,并且在某些时候这种方法变得不可行.

    • 解决方案是使用[array]实例来构建数组 - 见下文.

  • 在每次循环迭代中实例化新对象的速度有多快:

    • 实例化自定义类的一个实例,是不是通过哈希表字面创建一个实例快,但只有$ToWrite用于实例化 ; 功能相同的$ToWrite慢得多,由于涉及cmdlet的呼叫.

以下自定义类解决方案的$output += $row变体用于确保可接受的性能,即使迭代次数较多:

# Define the custom class.
class CsvRow {
  [object] ${Example Header 1}
  [object] ${Example Header 2}
}

# Determine the iteration count.
$count = 1000

# Loop and let PowerShell collect the outputs
# from all iterations implicitly in variable $array
[array] $array = foreach ($ndx in 1..$count) {
  # Instantiate the custom class.
  $rowObj = [CsvRow]::new()
  # Set the values.
  $rowObj.'Example Header 1' = "h1-$ndx"
  $rowObj.'Example Header 2' = "h2-$ndx"
  # Simply output the row object
  $rowObj
}

# Output the array.
$array
Run Code Online (Sandbox Code Playgroud)