在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地址的另一个映射arrays。 这代表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
有没有更好的方法来做到这一点?
Lee Holmes写了一篇关于Join-Object函数的博客文章 。 太糟糕了,它还没有内置到PowerShell中。
Join.ps1
Join-Object
(别名Join
)函数将来自两个对象数组的列合并到一个新的对象数组中,该对象数组可以保存为一个表格( Export-CSV
)或按照原样使用。
Function Join-Object { [CmdletBinding()]Param ( [PSObject[]]$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 = @{}; $Keys | ForEach {$Properties.$_ = $Null}; $Out = New-Object PSObject -Property $Properties $LeftOut = @($True) * @($LeftTable).Length; $RightOut = @($True) * @($RightTable).Length $NullObject = New-Object PSObject 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 {$Out.$_ = If ($LeftKeys -NotContains $_) {$Right.$_} ElseIf ($RightKeys -NotContains $_) {$Left.$_} Else {&$Merge.$_} }; $Out; $LeftOut[$LeftIndex], $RightOut[$RightIndex] = $Null } } } If ("LeftJoin", "FullJoin" -Contains $Type) { For ($LeftIndex = 0; $LeftIndex -lt $LeftOut.Length; $LeftIndex++) { If ($LeftOut[$LeftIndex]) {$Keys | ForEach {$Out.$_ = $LeftTable[$LeftIndex].$_}; $Out} } } If ("RightJoin", "FullJoin" -Contains $Type) { For ($RightIndex = 0; $RightIndex -lt $RightOut.Length; $RightIndex++) { If ($RightOut[$RightIndex]) {$Keys | ForEach {$Out.$_ = $RightTable[$RightIndex].$_}; $Out} } } }; 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指令类似。 默认的连接types是一个InnerJoin
。
-
InnerJoin-Object
(别名InnerJoin
)
返回两个表中具有匹配值的logging。 -
LeftJoin-Object
(别名LeftJoin
)
返回左表中的所有logging和右表中匹配的logging。 -
RightJoin-Object
(别名RightJoin
)
返回右表中的所有logging以及右表中匹配的logging。 -
FullJoin-Object
(别名FullJoin
)
当左侧或右侧表格匹配时,返回所有logging。
笔记
- 所有
Join
命令与PowerShell版本2和更高版本兼容。
参数
-LeftTable <Object[]>
和-RightTable <Object[]>
-LeftTable
和RightTable
参数定义了要连接的左右表。 有三种可能的语法来提供表格:
-
使用PowerShellpipe道:
<LeftTable> | Join <RightTable>
<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>
如果String -Equals <String>
值是一个String
并且提供了-Equals <String>
参数,-Equals <String>
值定义的左列中的属性要求等于右列定义的属性(内部)结果集中包含-equals
值。 -
String
或Array
如果值是String
或Array
则-On
参数与SQLusing
子句类似。 这意味着所有列出的属性需要相等(在左侧和右侧)以包含在(内部)结果集中。 列出的属性将默认输出单个值(另请参阅-Expressions
)。 -
ScriptBlock
任何条件expression式,其中$Left
定义左行,$Right
定义右行。
笔记
-
ScriptBlock
types具有最多的比较可能性,但比其他types慢得多。 -
如果
-On
参数被忽略或来自未知types,则将执行交叉连接 。
-Merge <HashTable>|<ScriptBlock>
定义如何合并具有相同名称的特定列。 -Merge
参数接受types:包含每个列的特定合并expression式的HashTable
或包含默认合并expression式的ScriptBlock
,该expression式没有定义任何合并expression式。
在expression式中:
-
$_
保存每个列名称。 -
$Left
保留左行,$Right
保存右行。 -
$Left.$_
保存每个左值和$Right.$_
保存每个正确的值。 -
$LeftIndex
保存当前左行索引,$RightIndex
保存当前右行索引。
笔记:
-
只有当左值(
Left.$_
)和右值(Left.$_
)都存在(包括$Null
值)时才执行expression式,否则只返回退出值。 -
如果没有为列定义expression式,则使用expression式
{$Left.$_, $Right.$_}
。 这意味着这两个值都被赋值(在一个数组中)到当前属性。 -
-Equals <String>
,--Equals <String>
和-Equals <String>
<Array>
定义的列的expression式是:{$Left.$_}
,只能由散列表中定义的列专用expression式否决。 这意味着一个单独的值($Left
或者$Right
不等于$Null
)被分配给当前属性。 -
要使用特定于列的expression式并定义默认expression式,请使用默认expression式的零长度键名称,例如
-Merge @{"" = {$Left.$_}; "Column Name" = {$Right.$_}}
-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}
更新服务列表(replace名称上的现有服务,并添加新的服务)
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