【问题标题】:Merge two CSV files while adding new and overwriting existing entries在添加新条目和覆盖现有条目时合并两个 CSV 文件
【发布时间】:2019-07-23 16:36:14
【问题描述】:

我有一个configuration.csv,其中包含这样的模板数据:

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item1 | value1 | ALL  |
| some/path  | item2 | UPDATE | ALL  |
| other/path | item1 | value2 | SOME |

和具有服务特定配置的customization.csv

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item2 | value3 | ALL  |
| new/path   | item3 | value3 | SOME |

我的目标是合并它们并最终得到这样的结果:

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item1 | value1 | ALL  |
| some/path  | item2 | value3 | ALL  |
| other/path | item1 | value2 | SOME |
| new/path   | item3 | value3 | SOME |

这应该添加任何新条目并更新任何现有条目。任何一列都不能用于唯一标识 - pathitem 都需要合并,因为它们保证是唯一的。

【问题讨论】:

    标签: powershell csv merge hashtable export-to-csv


    【解决方案1】:

    经过大量搜索,我想在不重新创建管理框架的情况下操作条目的最简单方法是通过。在此过程中,我必须考虑两种极端情况:

    1. 值中的附加逗号
    2. 空值

    我得到的最终解决方案是这样的:

    $configuration = Import-Csv .\configuration.csv
    $customization = Import-Csv .\customization.csv
    $merged = New-Object System.Collections.ArrayList
    $hashTable = @{}
    
    #initializing the hashTable with the defaults
    foreach ($entry in $configuration)
    {
        $hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
    }
    
    #updating the hashTable with customization that add or overwrite existing entries
    foreach ($entry in $customization)
    {
        $hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
    }
    
    #the regex handles multiple commas and empty values.
    #It returns an empty string before and after group so we start from 1 
    foreach ($key in $hashTable.keys)
    {
        $psobject = [PSCustomObject]@{
            path  = ($key -split '(.*),(.*)')[1]
            item  = ($key -split '(.*),(.*)')[2]
            value = ($hashTable[$key] -split '(.*),(.*)')[1]
            type  = ($hashTable[$key] -split '(.*),(.*)')[2]
        }
        [void] $merged.Add($psobject)
    }
    Write-Output $merged
    

    导入后,我将configuration.csv 转换为哈希表,其中键由pathvalue 组成。然后,我使用相同的哈希表对 customization.csv 执行相同操作,该哈希表会覆盖任何现有的 key 值或将它们添加为新值。

    第三个循环将 hashTable 转换为 PSCustomObject,类似于 Import-Csv 所做的。我拆分了每个 keyvalue 属性,同时考虑了多个逗号和空值。
    注意:正则表达式将在最后一次出现分隔符时拆分(这里是逗号,但你可以选择任何东西,真的)。如果要拆分第一个,可以使用(.*?),(.*)。在我的例子中,只有 value 列可以包含分隔符的实例。

    如果 CSV 具有唯一列,则可以使用类似于 this answer 的解决方案。

    另一种选择是将键设置为所有列的总和,这将过滤掉 CSV 中的所有重复项,但拆分可能会变得棘手,具体取决于列中的值。

    【讨论】:

    • 很好地使用哈希表,因为它们使用 binary search algorithm 确实很快,但我担心您的解决方案(您将相关项目视为字符串)可能不适用于可重用的解决方案.与您的情况一样,如果您的一个文件包含单引号(如Frank's File.txt)或用于分隔符的逗号,它可能会中断......
    • 我明白你的担忧。就我而言,我测试了多个逗号,但仅在左侧变量(pathvalue)中。其他两个预计不会有逗号。至于' - 我无法提出会破坏脚本的案例。它作为一个字符串被正确处理。
    • 我对引号的评论不正确(因为键字符串不会被扩展)但对逗号的关注仍然存在,为此您可能实际上考虑使用non-printable character 来分隔字段,例如:$entry.path + [char]31 + $entry.item(不太可能在任何属性名称中使用)。
    【解决方案2】:

    我建议使用Compare-Object 并且来自customization.csv 的值将持续使用此文件值作为-ReferenceObject

    ## Q:\Test\2019\03\01\SO_54948111.ps1
    
    $conf = Import-Csv '.\configuration.csv'
    $cust = Import-Csv '.\customization.csv'
    
    $NewData = Compare-Object -ref $cust -diff $conf -Property path,item -PassThru -IncludeEqual|
        Select-Object -Property * -ExcludeProperty SideIndicator
    
    $NewData
    $NewData |Export-Csv '.\NewData.csv' -NoTypeInformation
    

    样本输出

    > Q:\Test\2019\03\01\SO_54948111.ps1
    
    path       item  value  type
    ----       ----  -----  ----
    some/path  item2 value3 ALL
    some/path  item1 value1 ALL
    other/path item1 value2 SOME
    new/path   item3 value3 SOME
    

    【讨论】:

    • 很好的解决方案。我过去使用过Comprae-Object,但显然不足以注意到所有有用的技巧。
    【解决方案3】:

    您的想法“使用相同的哈希表覆盖任何现有的键值或将它们添加为新的。”只有在 path, item 在每一侧都是唯一的情况下才有效,因为您还将覆盖任何重复项... 考虑一下这个Join-Object cmdlet。

    $configuration =ConvertFrom-SourceTable'

    | path       | item  | value  | type |
    |------------|-------|--------|------|
    | some/path  | item1 | value1 | ALL  |
    | some/path  | item2 | UPDATE | ALL  |
    | other/path | item1 | value2 | SOME |
    | other/path | item1 | value3 | ALL  |
    '
    

    $customization=ConvertFrom-SourceTable'

    | path       | item  | value  | type |
    |------------|-------|--------|------|
    | some/path  | item2 | value3 | ALL  |
    | new/path   | item3 | value3 | SOME |
    | new/path   | item3 | value4 | ALL  |
    '
    

    使用Merge-Object,别名Merge,代理命令(见帮助):

    $configuration | Merge $customization -on path, item
    
    path       item  value  type
    ----       ----  -----  ----
    some/path  item1 value1 ALL
    some/path  item2 value3 ALL
    other/path item1 value2 SOME
    other/path item1 value3 ALL
    new/path   item3 value3 SOME
    new/path   item3 value4 ALL
    

    【讨论】:

    • 我在研究过程中看到了 cmdlet,但我想要的是自给自足且不依赖外部依赖的解决方案。
    • 您的想法“使用相同的哈希表覆盖任何现有的键值或将它们添加为新的。”将path, item 的情况下有效每一面都是独一无二的。在我的答案的更新示例中查看结果:stackoverflow.com/a/55543321/1701026
    • 这是我在问题中的初始请求 - 更新任何现有路径的值。由于这是配置文件,我需要每个条目都是唯一的并且不重复。您的解决方案很棒,但适用于不同的要求。感谢您指出其他问题。
    猜你喜欢
    • 1970-01-01
    • 2018-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-16
    相关资源
    最近更新 更多