CSV中重复行时合并和删除MINIMAL结果

时间:2018-11-28 11:50:24

标签: powershell csv merge duplicates

我们必须每天合并一些包含“计算机| 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()

2 个答案:

答案 0 :(得分: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 } }

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

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

第二个计算出的属性是计算内容:

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

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

这就是我通过组对象看到的测试数据:

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指定的属性。

答案 1 :(得分:0)

使用Join-Object cmdlet中的PowerShell Gallery

$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