【问题标题】:In Powershell, what's the best way to join two tables into one?在 Powershell 中,将两个表合并为一个的最佳方法是什么?
【发布时间】:2010-12-23 08:36:35
【问题描述】:

我是 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 应该获得第三个字段“名称”,只要有匹配的“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

有没有更好的方法?

【问题讨论】:

  • 令人难以置信的是,PowerShell 中还没有包含这个功能
  • @Michael 您如何获得所有活动租约的 IP 地址到主机名的表?我一直在使用 DHCP 模块,但无法弄清楚。我也在尝试合并一些表格。
  • @Ruisu 我使用netsh 的 dhcp 命令获取列表,然后使用正则表达式解析输出。还有DHCP Server Management API,但它仅适用于本机代码。我不知道有任何 powershell 模块或 .net 库可以做这种事情。我最终将 P/Invoke 包装器写入 DHCP 服务器管理 API。

标签: scripting powershell dhcp


【解决方案1】:

1.5 年后,我粘贴在原始答案中的 cmdlet 经历了如此多的更新,以至于它已经完全过时了。因此,我将codeReadMe 替换为最新版本的链接。

Join-Object

根据它们之间的相关属性组合两个对象列表。

说明
结合一个或多个对象的属性。它创建了一个可以保存为新对象或按原样使用的集合。对象连接是一种通过使用每个对象的共同值来组合来自一个(自连接)或多个对象列表的属性的方法。

主要特点

  • 直观(类似 SQL)的语法
  • 智能属性合并
  • 用于更新、合并和特定连接类型的预定义连接命令
  • 为(左)输入对象和输出对象定义明确的管道(正确使用时保留内存)
  • 在大型对象列表上的执行速度比 Compare-Object 快约 40%
  • 支持(自定义)对象、数据表和字典(例如哈希表)用于输入
  • 智能属性和计算属性表达式
  • 自定义关系表达式
  • 易于安装(点源)
  • 支持 PowerShell for Windows (5.1) 和 PowerShell Core

Join-Object cmdlet 显示以下代理命令及其自己的默认值(-JoinType-Property):

  • InnerJoin-Object(别名InnerJoinJoin),组合相关对象
  • LeftJoin-Object(别名LeftJoin),合并相关对象并添加剩余的左侧对象
  • RightJoin-Object(别名RightJoin),合并相关对象并添加其余正确对象
  • FullJoin-Object(别名FullJoin),合并相关对象,并添加剩下的左右对象
  • CrossJoin-Object(别名CrossJoin),将每个左侧对象与每个右侧对象组合在一起
  • Update-Object(别名Update),用相关的右对象更新左对象
  • Merge-Object(别名Merge),用相关的右侧对象更新左侧对象,并添加其余新的(不相关的)右侧对象

ReadMe

完整的自述文件(和源代码)可从 GitHub 获得:https://github.com/iRon7/Join-Object

安装

这个Join-Object cmdlet 有两个版本(两个版本提供相同的功能):

Install-Module -Name JoinModule

Install-Script -Name Join

(或将Join.psm1 模块重命名为Join.ps1 脚本文件)
并由dot sourcing调用脚本:

. .\Join.ps1

回答

回答问题中的实际示例:

$reservations |LeftJoin $leases -On IP

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

性能

关于性能测量的一点点:
PowerShell 管道旨在流式传输 对象(这可以保护内存),这意味着 输入对象的两个¹列表通常不(不应该)驻留在记忆。通常它们是从其他地方(即远程服务器、磁盘)检索的。此外,输出通常很重要,其中linq 的解决方案很快,但可能很容易让您在得出结论时走错路,因为linq 字面意思是延迟 em> 执行 (lazy evaluation),另请参阅:fastest way to get a uniquely index item from the property of an array
换句话说,如果涉及到(测量)PowerShell 的性能,重要的是查看完整的端到端解决方案,它更可能看起来像:

 import-csv .\reservations.csv |LeftJoin (import-csv .\leases.csv) -On IP |Export-Csv .\results.csv

(1) 注意:不幸的是,没有简单的方法来构建两个并行输入流(参见:#15206 Deferred input pipelines

(更多)示例

更多示例可以在相关的 Stackoverflow 问题中找到:

Join-Object test script

如果您支持Add a Join-Object cmdlet to the standard PowerShell equipment (#14994)的建议,请给个?

【讨论】:

  • 很棒的功能,但如果在 Powershell 模块中使用它,请小心。由于作用域的工作方式,$Left$Right 变量在-Merge 脚本块中不可用(模块的变量是模块私有的,因此脚本块看不到它们)。如果点源或直接在调用它的脚本中包含函数,这不是问题。
  • @Omni,我对将 cmdlet 放入模块进行了一些测试,但无法确认您的问题。然而,在修改 cmdlet 期间,我发现了一个相当大的错误,在 Unexpected results when reusing custom objects in the pipeline 中进行了描述。我创建了一个分支版本,粘贴在这个答案中。我怀疑这实际上是您遇到的问题(如果您能确认是否是这种情况,我将不胜感激)。
【解决方案2】:

你可以像这样使用脚本块

$leases | select IP, NAME, @{N='MAC';E={$tmp=$_.IP;($reservations| ? IP -eq $tmp).MAC}}

【讨论】:

    【解决方案3】:

    这是一个使用哈希表的简单示例。使用大数组,结果会更快。

    $leases =
    'IP,Name
    192.168.1.1,Apple
    192.168.1.2,Pear
    192.168.1.3,Banana
    192.168.1.99,FishyPC' | convertfrom-csv
    
    $reservations =
    'IP,MAC
    192.168.1.1,001D606839C2
    192.168.1.2,00E018782BE1
    192.168.1.3,0022192AF09C
    192.168.1.4,0013D4352A0D' | convertfrom-csv
    
    $hashRes=@{}
    foreach ($resRecord in $reservations) {
      $hashRes[$resRecord.IP] = $resRecord
    }
    
    $leases | foreach {
      $other = $hashRes[$_.IP]
    
      [pscustomobject]@{IP=$_.IP
                       MAC=$other.MAC
                      Name=$_.name}
    }
    
    IP           MAC          Name
    --           ---          ----
    192.168.1.1  001D606839C2 Apple
    192.168.1.2  00E018782BE1 Pear
    192.168.1.3  0022192AF09C Banana
    192.168.1.99              FishyPC
    

    【讨论】:

    • 我很喜欢这个,因为它的适应性也很强。我可能会改变的一件事,除非有我遗漏的根本原因,否则您不需要创建 $other 并且由于 ForEach 循环比到 ForEach-Object 的管道更快,您可能想要更改如何处理 $leases 以匹配您之前的循环。像这样...Foreach ($leaseRecord in $leases){[PSCustomObject]@{IP=$leaseRecord.IP; MAC=$hashRes[$leaseRecord.IP].MAC; Name=$leaseRecord.Name}}
    • 在与外连接相同的循环中创建内连接:$reservations_outer=$reservations.clone(); $leases | foreach { $lease=$_; $reservations_outer | where { $_.IP -eq $lease.IP} | add-member -notepropertymembers @{name=$lease.name} -passthru } -outvariable reservations_inner
    【解决方案4】:

    这也可以使用我的模块Join-Object来完成

    Install-Module 'Join-Object'
    
    Join-Object -Left $leases -Right $reservations -LeftJoinProperty 'IP' -RightJoinProperty 'IP'
    

    关于性能,我针对 100k 行的样本数据进行了测试:

    1. @js2010 发布的哈希表示例运行时间为 8 秒。
    2. Join-Object 由我在 14 秒内运行。
    3. LeftJoin by @iRon 运行时间为 1 分 50 秒

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-02
      • 2015-06-05
      • 2013-06-07
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      • 2010-11-15
      相关资源
      最近更新 更多