В Powershell, как лучше объединить две таблицы в одну?

Я довольно новичок в 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

Есть ли лучший способ сделать это?

6 ответов

Решение

Ли Холмс написал сообщение в блоге о функции Join-Object, которая делает то, что вы хотите. Жаль, что он еще не встроен в PowerShell.

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>]

команды

Join-Object (псевдоним Join) функция - это одна функция с несколькими псевдонимами, которая объединяет две таблицы (каждая из которых состоит из массива PSCustomObjects), аналогично соответствующим инструкциям SQL Join. Тип соединения по умолчанию InnerJoin,

  • InnerJoin-Object (псевдоним InnerJoin)
    Возвращает записи, которые имеют совпадающие значения в обеих таблицах.

  • LeftJoin-Object (псевдоним LeftJoin)
    Возвращает все записи из левой таблицы и сопоставленные записи из правой таблицы.

  • RightJoin-Object (псевдоним RightJoin)
    Возвращает все записи из правой таблицы и сопоставленные записи из правой таблицы.

  • FullJoin-Object (псевдоним FullJoin)
    Возвращает все записи, когда есть совпадение в левой или правой таблице.

Заметки

  1. Все Join Команды совместимы с PowerShell версии 2 и выше.

параметры

-LeftTable <Object[]> а также -RightTable <Object[]>

-LeftTable а также RightTable параметры определяют левую и правую таблицы для объединения. Существует три возможных синтаксиса для предоставления таблиц:

  • Использование конвейера PowerShell: <LeftTable> | Join <RightTable>

  • Предоставление обеих таблиц в массиве (разделенных запятой) в первой позиции аргумента: Join <LeftTable>,<RightTable>

  • Предоставление обеих таблиц с именованными аргументами: Join -Left <LeftTable> -Right <RightTable>

Заметки

  1. Если предоставляется только одна таблица (Join <Table>), самостоятельное объединение будет выполнено на столе.

-On <String>|<Array>|<ScriptBlock> а также -Equals <String>

-On (псевдоним Using) определяет условие, которое определяет, как объединять таблицы и какие строки включать в (внутренний) набор результатов. -On Параметр поддерживает следующие форматы:

  • String -Equals <String> Если -On значение является String и -Equals <String> параметры предоставляются, свойство в левом столбце определяется -On значение должно быть равно свойству в правом столбце, определяемом -equals значение, которое будет включено в (внутренний) набор результатов.

  • String или же Array Если значение является String или же Array -On параметр похож на SQL using пункт. Это означает, что все перечисленные свойства должны быть равными (слева и справа) для включения в (внутренний) набор результатов. Перечисленные свойства выведут одно значение по умолчанию (см. Также -Expressions).

  • ScriptBlock Любое условное выражение где $Left определяет левый ряд, $Right определяет правильный ряд.

Заметки

  1. ScriptBlock Тип имеет большинство возможностей сравнения, но значительно медленнее, чем другие типы.

  2. Если -On Параметр пропущен или имеет неизвестный тип, будет выполнено перекрестное соединение.

-Merge <HashTable>|<ScriptBlock>

Определяет, как конкретные столбцы с одинаковыми именами должны быть объединены. -Merge Параметр принимает типы: HashTable содержащий конкретное выражение слияния для каждого столбца или ScriptBlock содержащий выражение слияния по умолчанию для всех столбцов, для которых не задано выражение слияния.
Где в выражении:

  • $_ содержит имя каждого столбца.
  • $Left держит левый ряд и $Right держит правый ряд.
  • $Left.$_ содержит каждое левое значение и $Right.$_ содержит каждое правильное значение.
  • $LeftIndex содержит текущий индекс левой строки и $RightIndex содержит текущий индекс правой строки.

Заметки:

  1. Выражения выполняются только если оба левых значения (Left.$_) и правильное значение (Left.$_) существуют (включая значения, которые $Null) в противном случае возвращается только существующее значение.

  2. Если для столбца не определено выражение, выражение {$Left.$_, $Right.$_} используется. Это означает, что оба значения присваиваются (в массиве) текущему свойству.

  3. Выражение для столбцов, определяемых -On <String>, -Equals <String> и -на <Array> является: {$Left.$_} и может быть отменено только специфичным для столбца выражением, определенным в хеш-таблице. Это означает, что одно значение (либо $Left или же $Right который не равен $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

Обновлять список процессов и вставлять только процессы с более высоким процессором

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

Для последних Join-Object версию см. в галерее PowerShell или на сайте проекта по адресу: https://github.com/iRon7/Join-Object

This can also be done using my module Join-Object

      Install-Module 'Join-Object'

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

Regarding performance, I tested against a sample data of 100k lines:

  1. Hashtable example posted by js2010 run in 8 seconds.
  2. Join-Object by me run in 14 seconds.
  3. LeftJoin by iRon run in 1 minute and 50 seconds

Я нашел самый простой способ объединить два объекта Powershell с помощью ConvertTo-Json и ConvertFrom-Json.

Один лайнер на базе ОП Senario:

      $leases | foreach {(ConvertTo-Json $_) -replace ("}$", (ConvertTo-Json ($reservations | where IP -eq $_.IP | select * -ExcludeProperty IP)) -Replace "^{", ",")} 
| ConvertFrom-Json

Результат:

      IP          Name  Mac
--          ----  ---
192.168.1.1 Apple 001D606839C2
192.168.1.2 Pear  00E018782BE1

Для другого примера давайте создадим пару объектов:

      $object1 = [PSCustomObject]@{"A" = "1"; "B" = "2"}
$object2 = [PSCustomObject]@{"C" = "3"; "D" = "4"}

Объедините их вместе, используя Json, заменив открывающую и закрывающую скобки:

      (ConvertTo-Json $object1) -replace ("}$", $((ConvertTo-Json $object2) -Replace "^{", ",")) | ConvertFrom-Json

Выход:


Другой пример с использованием группы объектов:

      $mergedObjects = [PSCustomObject]@{"Object1" = $Object1; "Object2" = $Object2}
      Object1     Object2
-------     -------
@{A=1; B=2} @{C=3; D=4}

Можно просто сделать то же самое снова в foreach:

      $mergedObjects | foreach {(ConvertTo-Json $_.Object1) -replace ("}$", $((ConvertTo-Json $_.Object2) -Replace "^{", ",")) | ConvertFrom-Json}

Выход:

      A B C D
- - - -
1 2 3 4

Вы можете использовать такой блок скрипта

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

Вот простой пример использования хеш-таблицы. С большими массивами это оказывается быстрее.

      $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
Другие вопросы по тегам