【问题标题】:Merge and delete MINIMAL result when duplicate line in CSVCSV 中重复行时合并和删除 MINIMAL 结果
【发布时间】:2019-04-30 07:46:30
【问题描述】:

我们必须每天合并一些包含“Computer | Updates_Missing”的 CSV。但是为了保持这个文件更新并且没有重复的计算机,我想创建一个可以合并多个 CSV 并删除重复计算机的脚本,但 仅在以下情况下: 如果计算机是重复的,只保留计算机具有最低结果的行进行更新(或者如果重复导致更新也删除行)

我解释一下:

csv_day_1:

Computer_1 | 12
Computer_2 | 8
Computer_3 | 16
Computer_4 | 7

csv_day_2:

Computer_1 | 4
Computer_2 | 8
Computer_4 | 2
Computer_7 | 22

我希望最终结果是这样的:

Computer_1 | 4
Computer_2 | 8
Computer_3 | 16
Computer_4 | 2
Computer_7 | 22

我想要这样的模式:

  • Import-Csv 并选择“计算机”列
  • 如果计算机是重复的,请选择“Updates_missing”较少的行并删除其他计算机
  • 如果一台计算机多次得到相同的结果,只需保留一行。

这是一个 GUI 脚本,所以看起来像这样...:

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()

#region begin GUI{ 

$Form                            = New-Object system.Windows.Forms.Form
$Form.ClientSize                 = '600,300'
$Form.text                       = "Merge_CSV"
$Form.TopMost                    = $false
$Form.MaximizeBox                = $false
$Form.FormBorderStyle            = 'Fixed3D'

$Label1                          = New-Object system.Windows.Forms.Label
$Label1.text                     = "Browse your *.csv Files"
$Label1.AutoSize                 = $true
$Label1.width                    = 25
$Label1.height                   = 10
$Label1.location                 = New-Object System.Drawing.Point(40,20)
$Label1.Font                     = 'Arial,10'

$Button1                         = New-Object system.Windows.Forms.Button
$Button1.text                    = "Browse..."
$Button1.width                   = 100
$Button1.height                  = 30
$Button1.location                = New-Object System.Drawing.Point(60,50)
$Button1.Font                    = 'Arial,10'
$Button1.Add_Click({
    # Browse the files
    Add-Type -AssemblyName System.Windows.Forms
    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
        Multiselect = $true
        Filter = 'CSV Files (*.csv)|*.csv'
    }
    [void]$FileBrowser.ShowDialog()

    $path1 = $FileBrowser.FileNames
    foreach ($line in $path1){
        $TextBox2.Text += "$line"+"`r`n"
        }
})

$TextBox1                        = New-Object system.Windows.Forms.TextBox
$TextBox1.multiline              = $false
$TextBox1.width                  = 200
$TextBox1.height                 = 30
$TextBox1.location               = New-Object System.Drawing.Point(380,50)
$TextBox1.Font                   = 'Arial,10'

$Label2                          = New-Object system.Windows.Forms.Label
$Label2.text                     = "Name the exported file :"
$Label2.AutoSize                 = $true
$Label2.width                    = 25
$Label2.height                   = 10
$Label2.location                 = New-Object System.Drawing.Point(410,20)
$Label2.Font                     = 'Arial,10'

$Button2                         = New-Object system.Windows.Forms.Button
$Button2.text                    = "Fusionner et Convertir"
$Button2.width                   = 200
$Button2.height                  = 30
$Button2.location                = New-Object System.Drawing.Point(200,110)
$Button2.Font                    = 'Arial,11,style=bold'
$Button1.Add_Click({
    # 1 - Merge the file
    $CSV= @();
    Get-ChildItem $path1 | ForEach-Object{
        $CSV += @(Import-Csv -Delimiter ";" -Path $_)
        }
    $CSV | Export-Csv -Path C:\Temp\Fusion_CSV.csv -NoTypeInformation -Delimiter ";"

    # 2 - Clean the merge
    Import-csv C:\Temp\Fusion_CSV.csv -Delimiter ";" | Group-Object -Property "Computer"
})

$TextBox2                        = New-Object system.Windows.Forms.TextBox
$TextBox2.multiline              = $true
$TextBox2.width                  = 560
$TextBox2.height                 = 120
$TextBox2.location               = New-Object System.Drawing.Point(20,160)
$TextBox2.Font                   = 'Arial,9'

$Form.controls.AddRange(@($Label1,$Button1,$TextBox1,$Label2,$Button2,$TextBox2))

#endregion GUI }

[void]$Form.ShowDialog()

【问题讨论】:

    标签: powershell csv merge duplicates


    【解决方案1】:

    顺便说一句,这是一个糟糕的模式:

    $CSV = @();
    Get-ChildItem $path1 | ForEach-Object {
        $CSV += @(Import-Csv -Delimiter ";" -Path $_)
    }
    

    连接数组非常昂贵,应避免使用,因为 PowerShell 数组无法扩展。它必须在内存中复制整个数组并在每次添加新值时附加新数据。

    试试这个:

    $CSV = Get-ChildItem $path1 | Import-Csv -Delimiter ";"
    $CSV = $CSV | Group-Object -Property Computer | 
        Select-Object @{Name='Computer';Expression={$_.Name}}, @{Name='Updates_Missing';Expression={ $_.Group | Measure-Object -Minimum -Property Updates_Missing | Select-Object -ExpandProperty Minimum } }
    

    之后的 Select-Object 使用计算的属性来确定丢失的最小更新数。您需要小心缺失值或空值,因为它们可能会被解释为零。您可能需要使用Where-Object { -not [String]::IsNullOrWhiteSpace($_.Updates_Missing) } 之类的内容将它们过滤掉。您还必须注意 Updates_Missing 列中的任何非数字值。

    第一个计算属性 @{Name='Computer';Expression={$_.Name}} 只是将 Group-Object 输出中的 Name 列重命名为 Computer。 [注意:您可以指定@{n='Computer';e={$_.Name}}。为了清楚起见,我使用了计算属性元素的全名。]

    第二个计算属性是做什么的:

    @{Name='Updates_Missing';Expression={ $_.Group | Measure-Object -Minimum -Property Updates_Missing | Select-Object -ExpandProperty Minimum } }
    

    我们希望第二列的名称为Updates_Missing。不过,表达式更复杂。 Group-Object 输出中的Group 列是组中每个对象的集合。

    这是我在使用 Group-Object 时看到的测试数据:

    PS C:\> $CSV | Group-Object -Property Computer
    
    Count Name                      Group
    ----- ----                      -----
        2 Computer_1                {@{Computer=Computer_1; Updates_Missing=12}, @{Computer=Computer_1; Updates_Missing=4}}
        2 Computer_2                {@{Computer=Computer_2; Updates_Missing=8}, @{Computer=Computer_2; Updates_Missing=8}}
        2 Computer_3                {@{Computer=Computer_3; Updates_Missing=16}, @{Computer=Computer_3; Updates_Missing=16}}
        2 Computer_4                {@{Computer=Computer_4; Updates_Missing=7}, @{Computer=Computer_4; Updates_Missing=2}}
        1 Computer_7                {@{Computer=Computer_7; Updates_Missing=22}}
    

    我们只看第一条记录的Group

    PS C:\> ($CSV | Group-Object -Property Computer)[0].Group
    
    Computer   Updates_Missing
    --------   ---------------
    Computer_1 12
    Computer_1 4
    

    它是两个对象的集合。我们可以使用 Measure-Object 来找到最小值:

    PS C:\> ($CSV | Group-Object -Property Computer)[0].Group | Measure-Object -Property Updates_Missing -Minimum
    
    
    Count    : 2
    Average  :
    Sum      :
    Maximum  :
    Minimum  : 4
    Property : Updates_Missing
    

    请注意,Measure-Object 足够聪明,可以将其获得的字符串输入视为数值。这可能潜在地咬我们。例如,缺失值可能在输出中显示为零。您需要考虑到这一点。

    我们只需要最小值,而不是该度量对象的其余部分。所以:

    PS C:\> ($CSV | Group-Object -Property Computer)[0].Group | Measure-Object -Property Updates_Missing -Minimum | Select-Object -ExpandProperty Minimum
    4
    

    这就是你在第二个计算属性中的表达方式:

    @{Name='Updates_Missing';Expression={ $_.Group | Measure-Object -Minimum -Property Updates_Missing | Select-Object -ExpandProperty Minimum } }
    

    如果您有多个列,那么事情会变得有点困难。

    假设您的列现在是:计算机、IP 和 Updates_Missing。

    尝试类似:

    $CSV | Group-Object -Property Computer | 
        Select-Object @{Name = 'Computer'; Expression = {$_.Name}}, 
            @{Name = 'IP'             ; Expression = { $_.Group | Sort-Object -Property @{Expression = {[int]$_.Updates_Missing}} | Select-Object -ExpandProperty IP              -First 1 } },
            @{Name = 'Updates_Missing'; Expression = { $_.Group | Sort-Object -Property @{Expression = {[int]$_.Updates_Missing}} | Select-Object -ExpandProperty Updates_Missing -First 1 } }
    

    我再次改变了这里的逻辑。我们将不使用 Measure-Object,而是将 Sort-Object 与计算属性结合使用 Select-Object 以仅获取第一条记录。这样,当我们说 Computer_1 有 4 个 Missing_Updates 时,我们返回的 IP 就是该记录中缺少 4 个更新的 IP。您可以对后续字段重复相同的逻辑,仅更新您为 Select-Object -ExpandProperty 指定的属性名称和属性。

    【讨论】:

    • 你太好了,非常感谢!我只有最后一个问题......如果我有 3 列,并且我想保留第三列,我该怎么做?喜欢:“计算机,IP,Missing_Update”我不明白如何添加我的 excel 的其他列......再次,非常感谢你的工作,你太棒了!
    • 附带问题:为什么您撤消了我对问题中数据 sn-ps 的格式设置?我不认为对数据片段应用自动语法着色在大多数情况下有助于提高可读性,因此我通常将示例数据放在<pre>...</pre> 中,以便在视觉上区分数据和代码。
    • @AnsgarWiechers 因为我已经看到很多关于元数据的关于 <pre> 标签不起作用的投诉,我认为它们已被弃用。
    • @BaconBits :我实际上根据我的需要调整了您的代码,但它工作得很好!非常感谢你,如果没有你的帮助,我永远不会成功,如果你是朋友,我会付给你一杯咖啡或啤酒 :) !
    【解决方案2】:

    使用来自PowerShell GalleryJoin-Object cmdlet

    $day_1 = ConvertFrom-Csv 'Name,Value
    Computer_1,12
    Computer_2,8
    Computer_3,16
    Computer_4,7'
    
    $day_2 = ConvertFrom-Csv 'Name,Value
    Computer_1,4
    Computer_2,8
    Computer_4,2
    Computer_7,22'
    
    $day_1 | FullJoin $day_2 Name {[math]::Max([Int]$Left.$_, [Int]$Right.$_)}
    
    Value Name
    ----- ----
       12 Computer_1
        8 Computer_2
       16 Computer_3
        7 Computer_4
       22 Computer_7
    

    【讨论】:

    • 感谢您的回答!我想我更喜欢 Bacon Bits 的回答,但再次感谢您:)
    猜你喜欢
    • 2018-10-23
    • 2016-05-05
    • 2011-12-09
    • 2015-12-19
    • 2018-03-11
    • 2017-04-25
    • 1970-01-01
    • 1970-01-01
    • 2021-07-03
    相关资源
    最近更新 更多