代码之家  ›  专栏  ›  技术社区  ›  Michael Steele

在PowerShell中,将两个表合并为一个表的最佳方法是什么?

  •  14
  • Michael Steele  · 技术社区  · 14 年前

    我对PowerShell还比较陌生,想知道是否有人知道更好的方法来完成下面的示例问题。

    我有一个从IP地址到主机名的映射数组。这表示活动的DHCP租约列表:

    PS H:\> $leases
    
    IP                    Name
    --                    ----
    192.168.1.1           Apple
    192.168.1.2           Pear
    192.168.1.3           Banana
    192.168.1.99          FishyPC
    

    我有另一个从MAC地址到IP地址的映射数组。这表示IP保留列表:

    PS H:\> $reservations
    
    IP                    MAC
    --                    ---
    192.168.1.1           001D606839C2
    192.168.1.2           00E018782BE1
    192.168.1.3           0022192AF09C
    192.168.1.4           0013D4352A0D
    

    为了方便起见,我可以使用以下代码生成从MAC地址到IP地址和主机名的第三个映射数组。想法是 $reservations 应该得到第三个字段“name”,每当有匹配的“ip”字段时都会填充该字段:

    $reservations = $reservations | foreach {
        $res = $_
        $match = $leases | where {$_.IP -eq $res.IP} | select -unique
        if ($match -ne $NULL) {
            "" | select @{n="IP";e={$res.IP}}, @{n="MAC";e={$res.MAC}}, @{n="Name";e={$match.Name}}
        }
    }
    

    所需输出如下:

    PS H:\> $ideal
    
    IP                    MAC                 Name
    --                    ---                 ----
    192.168.1.1           001D606839C2        Apple
    192.168.1.2           00E018782BE1        Pear
    192.168.1.3           0022192AF09C        Banana
    192.168.1.4           0013D4352A0D
    

    有没有更好的办法?

    2 回复  |  直到 6 年前
        1
  •  9
  •   codekaizen    8 年前

    李福尔摩斯写了一篇 blog post on a Join-Object function 这就是你想要的。可惜它还没有内置到PowerShell中。

        2
  •  12
  •   iRon    6 年前

    Join-Object

    这个 Join-Object (别名) Join )函数将两个对象数组中的列组合成一个新的对象数组,该数组可以另存为表。( Export-CSV )或按原样使用。

    Function Join-Object {                                          # https://powersnippets.com/join-object/
        [CmdletBinding()]Param (                                    # Version 02.02.00, by iRon
            [Object[]]$RightTable, [Alias("Using")]$On, $Merge = @{}, [Parameter(ValueFromPipeLine = $True)][Object[]]$LeftTable, [String]$Equals
        )
        $Type = ($MyInvocation.InvocationName -Split "-")[0]
        $PipeLine = $Input | ForEach {$_}; If ($PipeLine) {$LeftTable = $PipeLine}
        If ($LeftTable -eq $Null) {If ($RightTable[0] -is [Array]) {$LeftTable = $RightTable[0]; $RightTable = $RightTable[-1]} Else {$LeftTable = $RightTable}}
        $DefaultMerge = If ($Merge -is [ScriptBlock]) {$Merge; $Merge = @{}} ElseIf ($Merge."") {$Merge.""} Else {{$Left.$_, $Right.$_}}
        If ($Equals) {$Merge.$Equals = {If ($Left.$Equals -ne $Null) {$Left.$Equals} Else {$Right.$Equals}}}
        ElseIf ($On -is [String] -or $On -is [Array]) {@($On) | ForEach {If (!$Merge.$_) {$Merge.$_ = {$Left.$_}}}}
        $LeftKeys  = @($LeftTable[0].PSObject.Properties  | ForEach {$_.Name})
        $RightKeys = @($RightTable[0].PSObject.Properties | ForEach {$_.Name})
        $Keys = $LeftKeys + $RightKeys | Select -Unique
        $Keys | Where {!$Merge.$_} | ForEach {$Merge.$_ = $DefaultMerge}
        $Properties = @{}; $LeftOut = @($True) * @($LeftTable).Length; $RightOut = @($True) * @($RightTable).Length
        For ($LeftIndex = 0; $LeftIndex -lt $LeftOut.Length; $LeftIndex++) {$Left = $LeftTable[$LeftIndex]
            For ($RightIndex = 0; $RightIndex -lt $RightOut.Length; $RightIndex++) {$Right = $RightTable[$RightIndex]
                $Select = If ($On -is [String]) {If ($Equals) {$Left.$On -eq $Right.$Equals} Else {$Left.$On -eq $Right.$On}}
                ElseIf ($On -is [Array]) {($On | Where {!($Left.$_ -eq $Right.$_)}) -eq $Null} ElseIf ($On -is [ScriptBlock]) {&$On} Else {$True}
                If ($Select) {$Keys | ForEach {$Properties.$_ = 
                        If ($LeftKeys -NotContains $_) {$Right.$_} ElseIf ($RightKeys -NotContains $_) {$Left.$_} Else {&$Merge.$_}
                    }; New-Object PSObject -Property $Properties; $LeftOut[$LeftIndex], $RightOut[$RightIndex] = $Null
        }   }   }
        If ("LeftJoin",  "FullJoin" -Contains $Type) {
            For ($LeftIndex = 0; $LeftIndex -lt $LeftOut.Length; $LeftIndex++) {
                If ($LeftOut[$LeftIndex]) {$Keys | ForEach {$Properties.$_ = $LeftTable[$LeftIndex].$_}; New-Object PSObject -Property $Properties}
        }   }
        If ("RightJoin", "FullJoin" -Contains $Type) {
            For ($RightIndex = 0; $RightIndex -lt $RightOut.Length; $RightIndex++) {
                If ($RightOut[$RightIndex]) {$Keys | ForEach {$Properties.$_ = $RightTable[$RightIndex].$_}; New-Object PSObject -Property $Properties}
        }   }
    }; Set-Alias Join   Join-Object
    Set-Alias InnerJoin Join-Object; Set-Alias InnerJoin-Object Join-Object -Description "Returns records that have matching values in both tables"
    Set-Alias LeftJoin  Join-Object; Set-Alias LeftJoin-Object  Join-Object -Description "Returns all records from the left table and the matched records from the right table"
    Set-Alias RightJoin Join-Object; Set-Alias RightJoin-Object Join-Object -Description "Returns all records from the right table and the matched records from the left table"
    Set-Alias FullJoin  Join-Object; Set-Alias FullJoin-Object  Join-Object -Description "Returns all records when there is a match in either left or right table"
    

    句法

    <Object[]> | InnerJoin|LeftJoin|RightJoin|FullJoin <Object[]> [-On <String>|<Array>|<ScriptBlock>] [-Merge <HashTable>|<ScriptBlock>] [-Eq <String>]

    InnerJoin|LeftJoin|RightJoin|FullJoin <Object[]>,<Object[]> [-On <String>|<Array>|<ScriptBlock>] [-Merge <HashTable>|<ScriptBlock>] [-Eq <String>]

    InnerJoin|LeftJoin|RightJoin|FullJoin -LeftTable <Object[]> -RightTable <Object[]> [-On <String>|<Array>|<ScriptBlock>] [-Merge <HashTable>|<ScriptBlock>] [-Eq <String>]

    命令

    这个 连接对象 (别名) 加入 )函数是一个具有多个别名的函数,用于联接两个表(每个表由一个 PSCustomObjects )类似于各自的 SQL Join 指令。默认值 join type 是一个 InnerJoin .

    • InnerJoin-Object (别名) 内连接 )
      返回两个表中具有匹配值的记录。

    • LeftJoin-Object (别名) LeftJoin )
      返回左表中的所有记录和右表中匹配的记录。

    • RightJoin-Object (别名) RightJoin )
      返回右表中的所有记录和右表中匹配的记录。

    • FullJoin-Object (别名) FullJoin )
      当左表或右表中有匹配项时返回所有记录。

    笔记

    1. 所有 加入 命令与PowerShell版本2及更高版本兼容。

    参数

    -LeftTable <Object[]> -RightTable <Object[]>

    这个 -LeftTable RightTable 参数定义要联接的左表和右表。提供这些表有三种可能的语法:

    • 使用PowerShell管道: <LeftTable> | Join <RightTable>

    • 在第一个参数位置提供数组中的两个表(用逗号分隔): Join <LeftTable> , <RightTable>

    • 为两个表提供命名参数: Join -Left <LeftTable> -Right <RightTable>

    笔记

    1. 如果只提供一个表( Join <Table> 一个自我 self-join 将在桌子上执行。

    -On <String>|<Array>|<ScriptBlock> -Equals <String>

    这个 -On (别名) Using )参数定义条件,该条件指定如何联接表以及要在(内部)结果集中包括哪些行。这个 -关于 参数支持以下格式:

    • String -Equals <String> 如果 -关于 值是 String 以及 -等于<字符串> 提供了参数,左列中的属性由 -关于 值需要等于由定义的右列中的属性 -equals 要包含在(内部)结果集中的值。

    • Array 如果值是 字符串 数组 这个 -关于 参数类似于SQL using 条款。这意味着所有列出的属性都需要相等(在左侧和右侧),才能包含在(内部)结果集中。默认情况下,列出的属性将输出单个值(另请参见 -Expressions )

    • ScriptBlock 任何条件表达式,其中 $Left 定义左行, $Right 定义右行。

    笔记

    1. 这个 脚本块 类型具有最大的比较可能性,但比其他类型慢得多。

    2. 如果 -关于 参数被省略或来自未知类型,a cross-join 将执行。

    -Merge <HashTable>|<ScriptBlock>

    定义如何合并具有相同名称的特定列。这个 -Merge 参数接受类型:a HashTable 包含每列的特定合并表达式或 脚本块 包含未定义合并表达式的所有列的默认合并表达式。
    表达式中的位置:

    • $_ 保留每个列名称。
    • 左转 保持左边一排 $右 保持右行。
    • $Left.$_ 保留每个左值和 $Right.$_ 保留每个正确的值。
    • $LeftIndex 保留当前的左行索引和 $RightIndex 保留当前右行索引。

    笔记:

    1. 只有当两个左值都为( Left.$_ )和正确的价值( )存在(包括 $Null )否则只返回退出值。

    2. 如果没有为列定义表达式,则表达式 {$Left.$_, $Right.$_} 使用。 这意味着两个值(在数组中)都被分配给当前属性。

    3. 由定义的列的表达式 -On <String> , -等于<字符串> 和- <Array> 是: {$Left.$_} 并且只能由哈希表中定义的列特定表达式覆盖。 这意味着单个值(或者 左转 $右 不等于 $null )分配给当前属性。

    4. 使用列特定表达式 定义默认表达式使用零长度的键名称作为默认表达式,例如 -Merge @{"" = {$Left.$_}; "Column Name" = {$Right.$_}}

    实例

    给出下表:

       $Employee                               $Department
    +---------+---------+-------------+    +-------------+---------+---------+
    |  Name   | Country | Department  |    |    Name     | Country | Manager |
    +---------+---------+-------------+    +-------------+---------+---------+
    | Aerts   | Belgium | Sales       |    | Engineering | Germany | Meyer   |
    | Bauer   | Germany | Engineering |    | Marketing   | England | Morris  |
    | Cook    | England | Sales       |    | Sales       | France  | Millet  |
    | Duval   | France  | Engineering |    +-------------+---------+---------+
    | Evans   | England | Marketing   |
    | Fischer | Germany | Engineering |
    +---------+---------+-------------+
    
    PS C:\> # InnerJoin on Department = Name
    PS C:\> $Employee | InnerJoin $Department Department -eq Name | Format-Table
    
    Department  Name    Manager Country
    ----------  ----    ------- -------
    Sales       Aerts   Millet  {Belgium, France}
    Engineering Bauer   Meyer   {Germany, Germany}
    Sales       Cook    Millet  {England, France}
    Engineering Duval   Meyer   {France, Germany}
    Marketing   Evans   Morris  {England, England}
    Engineering Fischer Meyer   {Germany, Germany}
    
    
    PS C:\> # LeftJoin using country (selecting Department.Name and Department.Country)
    PS C:\> $Employee | LeftJoin ($Department | Select Manager,Country) Country | Format-Table
    
    Department  Name    Manager Country
    ----------  ----    ------- -------
    Engineering Bauer   Meyer   Germany
    Sales       Cook    Morris  England
    Engineering Duval   Millet  France
    Marketing   Evans   Morris  England
    Engineering Fischer Meyer   Germany
    Sales       Aerts           Belgium
    
    
    PS C:\> # InnerJoin on Employee.Department = Department.Name and Employee.Country = Department.Country (returning only the left name and - country)
    PS C:\> $Employee | InnerJoin $Department {$Left.Department -eq $Right.Name -and $Left.Country -eq $Right.Country} {$Left.$_}
    
    Department  Name    Manager Country
    ----------  ----    ------- -------
    Engineering Bauer   Meyer   Germany
    Marketing   Evans   Morris  England
    Engineering Fischer Meyer   Germany
    
    
    PS C:\> # Cross Join
    PS C:\> $Employee | InnerJoin $Department | Format-Table
    
    Department  Name                   Manager Country
    ----------  ----                   ------- -------
    Sales       {Aerts, Engineering}   Meyer   {Belgium, Germany}
    Sales       {Aerts, Marketing}     Morris  {Belgium, England}
    Sales       {Aerts, Sales}         Millet  {Belgium, France}
    Engineering {Bauer, Engineering}   Meyer   {Germany, Germany}
    Engineering {Bauer, Marketing}     Morris  {Germany, England}
    Engineering {Bauer, Sales}         Millet  {Germany, France}
    Sales       {Cook, Engineering}    Meyer   {England, Germany}
    Sales       {Cook, Marketing}      Morris  {England, England}
    Sales       {Cook, Sales}          Millet  {England, France}
    Engineering {Duval, Engineering}   Meyer   {France, Germany}
    Engineering {Duval, Marketing}     Morris  {France, England}
    Engineering {Duval, Sales}         Millet  {France, France}
    Marketing   {Evans, Engineering}   Meyer   {England, Germany}
    Marketing   {Evans, Marketing}     Morris  {England, England}
    Marketing   {Evans, Sales}         Millet  {England, France}
    Engineering {Fischer, Engineering} Meyer   {Germany, Germany}
    Engineering {Fischer, Marketing}   Morris  {Germany, England}
    Engineering {Fischer, Sales}       Millet  {Germany, France}
    

    更新服务列表(替换名称上的现有服务并添加新服务)

    Import-CSV .\Svc.csv | LeftJoin (Get-Service) Name {$Right.$_} | Export-CSV .\Svc.csv
    

    更新进程列表,仅插入具有较高CPU的进程

    Import-CSV .\CPU.csv | LeftJoin (Get-Process) ID {If ($Left.CPU -gt $Right.CPU) {$Left.$_} Else {$Right.$_}} | Export-CSV .\CPU.csv
    

    最新的 连接对象 版本,请参见 PowerShell Gallery 或项目现场: https://github.com/iRon7/Join-Object