繁体   English   中英

使用 Powershell 创建/填充 csv 文件

[英]Create/populate a csv file with 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

我正在使用$output | Export-Csv new.csv -NoTypeInformation将其写入文件$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"
    }
}

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

我再次尝试使用$output += $row添加更多行,但它根本不起作用并导致一些奇怪的错误发生(如果我写入一行,它会写入所有行,可能是因为它们都是同一个对象)。

事实上,这就是发生的事情:在 .NET 术语中,类型(类) [pscustomobject]引用类型而不是值类型——正如[pscustomobject].IsValueType返回$false所证明的[pscustomobject].IsValueType

如果添加引用类型的给定实例(对象)的阵列多次,所有这样的元件指向非常相同的实例

这里有一个简短的演示。

$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

这产生以下结果:


Example Header 1 Example Header 2
---------------- ----------------
h1-2             h2-2
h1-2             h2-2

如您所见,只有分配给.Example Header 1.Example Header 2最后一个值生效,因为两个数组元素都引用了同一个对象。


基于类的解决方案

Martin 的方法是解决此问题的最简单方法在每次迭代中创建自定义对象的新实例(通过哈希表文字语法,如问题本身所示: $array += [pscustomobject] @{ ... } )。

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

  • 克隆在每次循环或者干脆使用模板自定义对象[pscustomobject] @{ ... }循环,这隐式地创建一个新的实例每次的时间创建对象

  • 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

性能注意事项

两个因素决定性能:

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

    • 使用$array += ...逐个元素扩展数组非常方便,但是速度慢且效率低,因为每次都必须创建一个新数组(数组是固定大小的集合,不能直接扩展)。

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

    • 下一个最佳解决方案是使用[System.Collections.Generic.List[object]]实例来构建数组 - 此类列表旨在有效扩展。

    • 然而,最好和最简单的解决方案是简单地让PowerShell从数组中的类似循环的语句中收集多个输出,只需分配给一个变量- 见下文。

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

    • 实例化的自定义类的一个实例是比通过散列表字面创建实例更快的,但只有[CsvRow]::new()被用于实例化; 由于涉及cmdlet 调用功能上等效的New-Object CsvRow速度要慢得多

自定义类解决方案的以下变体使用隐式数组创建来确保可接受的性能,即使具有更高的迭代计数

# 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

注意: [array]类型约束仅在需要确保$ToWrite始终为数组时才需要; 没有它,如果碰巧只是一个单一的循环迭代,因此输出对象, $ToWrite将存储输出对象,是不是包裹在一个阵列(这种行为是根本PowerShell的管道)。

正如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

输出:

"Example Header 1","Example Header 2","Example Header 3"
"a","b","c"
"a2","b2","c2"
"a3","b4","c5"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM