【问题标题】:Converting CSV Column to Rows将 CSV 列转换为行
【发布时间】:2018-06-12 14:24:22
【问题描述】:

我正在尝试创建一个将 CSV 文件从列转换为行的 PowerShell 脚本。

我发现了相反的例子(将基于行的 CSV 转换为列),但我没有发现列到行的任何内容。我的问题是我不知道我将拥有多少列。我尝试调整行到列到列到行,但没有成功。

$a = Import-Csv "input.csv"
$a | FT -AutoSize

$b = @()
foreach ($Property in $a.Property | Select -Unique) {
    $Props = [ordered]@{ Property = $Property }
    foreach ($Server in $a.Server | Select -Unique){ 
        $Value = ($a.where({ $_.Server -eq $Server -and 
                    $_.Property -eq $Property })).Value
        $Props += @{ $Server = $Value }
    }
    $b += New-Object -TypeName PSObject -Property $Props
}

$b | FT -AutoSize
$b | Out-GridView
$b | Export-Csv "output.csv" -NoTypeInformation 

例如,我的 CSV 可能如下所示:

"ID","DATA1" "12345","11111" "54321","11111" "23456","44444"

或者这个(列数可以变化):

"ID","DATA1","DATA2","DATA3" "12345","11111","22222","33333" "54321","11111",, "23456","44444","55555",

我希望脚本能像这样转换它:

"ID","数据" "12345","11111" "12345","22222" "12345","33333" "54321","11111" "23456","44444" "23456","55555"

【问题讨论】:

  • 嗯......所以你所要求的并不是你所说的你想要的。将列转换为行不会给出您显示的输出。您想要的是将每个 ID 的一行转换为每个 ID 的多行。有几种方法可以解决这个问题。 ID 总是会是第一列吗?
  • 是的,它永远是第一列
  • 另外,数据是否有可能在字段中包含逗号?
  • 不,字段中不会有任何逗号。

标签: powershell csv


【解决方案1】:

诀窍是查询表的成员以获取列名。一旦你这样做了,剩下的就很简单了:

function Flip-Table ($Table) {

    Process {

        $Row = $_

        # Get all the columns names, excluding the ID field.
        $Columns = ($Row | Get-Member -Type NoteProperty | Where-Object Name -ne ID).Name

        foreach ($Column in $Columns) {

            if ($Row.$Column) {

                $Properties = [Ordered] @{
                    "ID"   = $Row.ID
                    "DATA" = $Row.$Column
                }

                New-Object PSObject -Property $Properties
            }
        }

        # Garbage collection won't kick in until the end of the script, so
        # invoke it every 100 input rows.

        $Count++;

        if (($Count % 100) -eq 0) {
            [System.GC]::GetTotalMemory('forceFullCollection') | out-null
        }
    }
}

Import-Csv input.csv | Flip-Table | Export-Csv -NoTypeInformation output.csv

【讨论】:

  • 再次感谢,这很好用。但是我确实有一个我处理的文件非常大,导致脚本使用大量 RAM(实际上必须杀死其他一些进程以避免脚本崩溃)。是否可以调整它以在输出文件中写入一些行,然后在恢复之前释放一些内存。我想这会减慢进程,但避免在处理大文件时需要大量内存并有崩溃的风险。
  • @FrancoisA 我已经更改了函数,以便它可以流式传输数据。据我所知,这应该可以解决 RAM 问题。请注意,现在在最后一行中以不同的方式调用它。
  • 不幸的是,即使使用那个新版本,高内存使用问题仍然存在 抛出“System.OutOfMemoryException”类型的异常。在 C:\Script\transposeRowsGroups.ps1:19 char:17 + if ($Row.$Column) { + + CategoryInfo : OperationStopped: (:) [], OutOfMemoryException + FullyQualifiedErrorID : System.OutOfMemoryException
  • 好的 - 我刚刚阅读了 PowerShell 中的垃圾收集并添加了手动垃圾收集步骤。我希望这能解决内存分配问题。
  • 非常感谢。好的,它不起作用。但这是因为在使用管道调用时垃圾收集器存在问题,我不得不将 [System.GC]::Collect() 更改为 [System.GC]::GetTotalMemory(‘forcefullcollection’) | out-null 所以现在它可以工作了。谢谢你的帮助 ! GC IssueWork-Around
【解决方案2】:

嗯,这是我的。我不像其他人那么花哨:

$in = Get-Content input.csv | Select -Skip 1
$out = New-Object System.Collections.ArrayList
foreach($row in $in){
    $parts = $row.Split(',')
    $id = $parts[0]
    foreach($data in $parts[1..$parts.Count]){
        if($data -ne '' -AND $data -ne $null){
            $temp = New-Object PSCustomObject -Property @{'ID' = $id;
                                                        'Data' = $data}
            $out.Add($temp) | Out-Null
        }
    }

}
$out | Export-CSV output.csv -NoTypeInformation

【讨论】:

    【解决方案3】:

    你可以这样做

    # Convert csv to object
    $csv = ConvertFrom-Csv @"
    "ID","DATA1","DATA2","DATA3"
    "12345","11111","22222","33333"
    "54321","11111",,
    "23456","44444","55555"
    "@
    
    # Ignore common members and the ID property
    $excludedMembers = @(
        'GetHashCode',
        'GetType',
        'ToString',
        'Equals',
        'ID'
    )
    
    $results = @()
    
    # Iterate around each csv row
    foreach ($row in $csv) {
        $members =  $row | Get-Member
        # Iterate around each member from the 'row object' apart from our 
        # exclusions and empty values
        foreach ($member in $members | 
            Where { $excludedMembers -notcontains $_.Name -and $row.($_.Name)}) {
            # add to array of objects
            $results += @{ ID=$row.ID; DATA=$row.($member.Name)}
        }
    }
    
    # Write the csv string
    $outstring = "ID,DATA"
    $results | foreach { $outstring += "`n$($_.ID),$($_.DATA)" }
    
    # New csv object
    $csv = $outstring | ConvertFrom-Csv
    

    可能不是最优雅的解决方案,但应该做你需要的

    我留下了一些 cmets 解释它的作用

    【讨论】:

      【解决方案4】:

      如果您只想接受有限数量的 DATA 列(例如 5 个),您可以这样做:

      ForEach ($i in 1..5) {$CSV | ? {$_."Data$i"} | Select ID, @{N='Data'; E={$_."Data$i"}}}
      

      如果您有无限数量的DATA 列:

      ForEach ($Data in ($CSV | Select "Data*" -First 1).PSObject.Properties.Name) {
          $CSV | ? {$_.$Data} | Select ID, @{N='Data'; E={$_.$Data}}
      }
      

      【讨论】:

        猜你喜欢
        • 2014-07-20
        • 1970-01-01
        • 2013-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-01
        • 2019-12-17
        • 1970-01-01
        相关资源
        最近更新 更多