В 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
)
Возвращает все записи, когда есть совпадение в левой или правой таблице.
Заметки
- Все
Join
Команды совместимы с PowerShell версии 2 и выше.
параметры
-LeftTable <Object[]>
а также -RightTable <Object[]>
-LeftTable
а также RightTable
параметры определяют левую и правую таблицы для объединения. Существует три возможных синтаксиса для предоставления таблиц:
Использование конвейера PowerShell:
<LeftTable> | Join <RightTable>
Предоставление обеих таблиц в массиве (разделенных запятой) в первой позиции аргумента:
Join <LeftTable>,<RightTable>
Предоставление обеих таблиц с именованными аргументами:
Join -Left <LeftTable> -Right <RightTable>
Заметки
- Если предоставляется только одна таблица (
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
параметр похож на SQLusing
пункт. Это означает, что все перечисленные свойства должны быть равными (слева и справа) для включения в (внутренний) набор результатов. Перечисленные свойства выведут одно значение по умолчанию (см. Также-Expressions
).ScriptBlock
Любое условное выражение где$Left
определяет левый ряд,$Right
определяет правильный ряд.
Заметки
ScriptBlock
Тип имеет большинство возможностей сравнения, но значительно медленнее, чем другие типы.Если
-On
Параметр пропущен или имеет неизвестный тип, будет выполнено перекрестное соединение.
-Merge <HashTable>|<ScriptBlock>
Определяет, как конкретные столбцы с одинаковыми именами должны быть объединены. -Merge
Параметр принимает типы: HashTable
содержащий конкретное выражение слияния для каждого столбца или ScriptBlock
содержащий выражение слияния по умолчанию для всех столбцов, для которых не задано выражение слияния.
Где в выражении:
$_
содержит имя каждого столбца.$Left
держит левый ряд и$Right
держит правый ряд.$Left.$_
содержит каждое левое значение и$Right.$_
содержит каждое правильное значение.$LeftIndex
содержит текущий индекс левой строки и$RightIndex
содержит текущий индекс правой строки.
Заметки:
Выражения выполняются только если оба левых значения (
Left.$_
) и правильное значение (Left.$_
) существуют (включая значения, которые$Null
) в противном случае возвращается только существующее значение.Если для столбца не определено выражение, выражение
{$Left.$_, $Right.$_}
используется. Это означает, что оба значения присваиваются (в массиве) текущему свойству.Выражение для столбцов, определяемых
-On <String>
,-Equals <String>
и -на<Array>
является:{$Left.$_}
и может быть отменено только специфичным для столбца выражением, определенным в хеш-таблице. Это означает, что одно значение (либо$Left
или же$Right
который не равен$Null
) присваивается текущему свойству.Чтобы использовать специфичные для столбца выражения и определить выражение по умолчанию, используйте имя ключа нулевой длины для выражения по умолчанию, например
-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:
Я нашел самый простой способ объединить два объекта 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