Gyűjtemények összehasonlítása (Compare-Object)

A Compare-Object  cmdlet segítségével két tetszőleges gyűjteményt hasonlíthatunk össze, kimenetül a gyűjtemények közötti különbséget leíró objektumokat kapunk. Az alábbi példában egy változóba mentettem a gépen futó folyamatok listáját. Ezután leállítottam, illetve elindítottam néhány folyamatot, majd ezt az új állapotot egy másik változóba írtam. A Compare-Object-nek odaadtam a két változót, ő pedig kilistázta a különbségeket:

[44] PS C:\> $a = Get-Process # bezár notepad, xmlnotepad

[45] PS C:\> $b = Get-Process # megnyit másik notepad példány, mspaint

[46] PS C:\> Compare-Object $a $b

 

InputObject                          SideIndicator

-----------                          -------------

System.Diagnostics.Process (mspaint) =>

System.Diagnostics.Process (XmlNo... <=

A [44]-es sor futtatásakor a Notepad egy példánya futott, meg az XMLnotepad program. A [45]-ös sor futtatása előtt becsuktam a Notepadet és újra megnyitottam (másik processz lett belőle), és becsuktam az XMLnotepad-et és megnyitottam az MSPaint-et. A [46]-os sorban összehasonlítottam a két állapotban mintavételezett processzek listáját. Valamilyen szempontból jó eredményt kaptunk, de ha igazán belegondolunk, és precízek szerettünk volna lenni, akkor ez mégsem jó eredmény, hiszen a két notepad.exe folyamat az nem ugyanaz. Vajon hogyan gondolkodott a PowerShell? Szegény compare-object bármilyen bonyolult objektumok gyűjteményét kaphatja paraméterként, így ha az objektumok összes tulajdonságának összehasonlításával döntené el az egyezőséget, akkor nagyon sokat kellene dolgoznia. Így alaphelyzetben nagyon egyszerű algoritmust alkalmaz: veszi az objektumok szöveggé alakított formáját. A ToString  metódus minden objektumnál kötelező elem, így ez garantáltan meghívható. Nézzük meg, hogy ez mit az a fenti esetben:

[47] PS C:\> $a | ForEach-Object {$_.tostring()}

System.Diagnostics.Process (conhost)

System.Diagnostics.Process (csrss)

System.Diagnostics.Process (csrss)

System.Diagnostics.Process (dfsrs)

System.Diagnostics.Process (dfssvc)

Hiszen pont ilyen adatokat láthatunk a [46]-os sor futtatása után, és ebben tényleg csak a processz objektumok típusa és neve látszik, azaz elrejtődik, ha egy processzt újra nyitunk. Hogyan lehetne precízebbé tenni a compare-object-et? Használjuk a –property paramétert, ahol felsorolhatjuk, hogy pontosan mely tulajdonság(ok) összehasonlításán alapuljon az egyes objektumok egyformaságának eldöntése. A mi esetünkben legyen a processzazonosító és a processz neve:

[48] PS C:\> Compare-Object $a $b -Property id, name

 

                      id name                     SideIndicator

                      -- ----                     -------------

                    2076 mspaint                  =>

                    1632 notepad                  =>

                    2944 notepad                  <=

                     396 XmlNotepad               <=

Ez már precízebb eredményt adott!

Mire használható ez még? Szeretnénk például megtudni, hogy az internetről gyűjtött csodaprogram telepítője pontosan mit garázdálkodik a gépünkön? Semmi gond, készítsünk pillanatfelvételt az érzékeny területekről (futó folyamatok, fájlrendszer, registry) a telepítés előtt, majd hasonlítsuk össze a telepítőprogram lefutása utáni állapottal. Lesz nagy meglepetés! Nem kell elaprózni, bátran lekérhetjük például a teljes c: meghajtó állapotát, a gép majd beleizzad kicsit az összehasonlításba, de így mindenre fény derül:

PS C:\> $a = Get-ChildItem c: -recurse

PS C:\> $b = Get-ChildItem c: -recurse

PS C:\> Compare-Object $a $b

Vigyázzunk azonban a compare-object-tel! Ha túl sok a különbség a két gyűjtemény között, akkor elég sokáig eltarthat az összehasonlítgatás, hiszen az első kupac minden eleméhez megnézi, hogy van-e egyező elem a másik kupacban. Ha mindkét kupac közel azonos számú elemből áll és az elemek sorrendben vannak, akkor használhatjuk –SyncWindow paramétert, mellyel leszűkíthetjük azt a tartományt, amin belül egyezést keres a másik kupacban. Nézzünk erre egy példát:

[56] PS C:\> Compare-Object 1,2,3 3,4,5 -SyncWindow 1

 

                         InputObject SideIndicator

                         ----------- -------------

                                   3 =>

                                   1 <=

                                   4 =>

                                   2 <=

                                   5 =>

                                   3 <=

A fenti példában az egyik gyűjteményem 1-től 3-ig a számok, a másik gyűjteményem 3-tól 5-ig. Azaz a 3 valójában nem különbség a két gyűjtemény között, mégis az eredményben, ami ugye az eltéréseket adja meg, a 3-as is szerepel, ráadásul kétszer is. Ennek az az oka, hogy a compare‑object 1-es synwindow paraméterrel nem minden elemhez néz meg minden elemet, hanem alaphelyzetben ±1 elem távolságra. Azaz az első halmaz 3-asát összehasonlítja a második tömbbeli 4-gyel, 5-tel, de az ottani első 3-assal már nem. A compare-object alaphelyzetben ±maxint távolságra vizsgál, azaz gyakorlatilag minden elemhez minden elemet megkeres:

[57] PS C:\> Compare-Object 1,2,3 3,4,5

 

                         InputObject SideIndicator

                         ----------- -------------

                                   4 =>

                                   5 =>

                                   1 <=

                                   2 <=

Így megtalálta, hogy mindkét halmazban benne van a 3-as.

A compare-object akár több tulajdonság együttállását is képes összehasonlítani:

PS C:\> $a = Get-Process

PS C:\> $b = Get-Process

PS C:\> Compare-Object $a $b -Property id, workingset

 

                        id                workingset SideIndicator

                        --                ---------- -------------

                      3564                  59248640 =>

                       348                  62193664 =>

                      2904                   6524928 =>

                      3564                  61214720 <=

                       348                  62160896 <=

                      2904                   6508544 <=

A fenti példában 1-2 másodperces eltéréssel vettem mintát a futó processzeimből, ha csak ID-re vizsgáltam volna, akkor nem lett volna különbség a két kupac között, de így, hogy ID-re és workingset-re együtt vizsgáltam, így már látható, hogy három processznek változott meg a memóriafelhasználása ilyen rövid idő alatt.

Ha tovább vizsgáljuk az így kapott kimenetet, akkor láthatjuk, hogy annak szerkezete eléggé eltávolodott az eredeti objektumok típusától:

PS C:\> Compare-Object $a $b -Property id, workingset | gm

 

 

   TypeName: System.Management.Automation.PSCustomObject

 

Name          MemberType   Definition

----          ----------   ----------

Equals        Method       bool Equals(System.Object obj)

GetHashCode   Method       int GetHashCode()

GetType       Method       type GetType()

ToString      Method       string ToString()

id            NoteProperty System.Int32 id=3564

SideIndicator NoteProperty System.String SideIndicator==>

workingset    NoteProperty System.Int32 workingset=59248640

De vajon hogyan lehetne valahogy megőrizni a kimenetben is a processz objektumokat? Erre –PassThru paraméter ad megoldást:

PS C:\> Compare-Object $a $b -Property id, workingset -PassThru

 

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

    366      23    58216      57860   574     1,98   3564 powershell

   1493      77    49944      60736   507             348 svchost

     76       7     2864       6372    31            2904 unsecapp

    463      24    60192      59780   574     2,00   3564 powershell

   1481      77    49840      60704   506             348 svchost

     74       7     2836       6356    30            2904 unsecapp

Itt viszont nem látom a változás irányát! De szerencsére azért az ott van a mélyén:

PS C:\> Compare-Object $a $b -Property id, workingset -PassThru | ft id, proces

sname, workingset, sideindicator

 

                 Id ProcessName                  WorkingSet SideIndicator

                 -- -----------                  ---------- -------------

               3564 powershell                     59248640 =>

                348 svchost                        62193664 =>

               2904 unsecapp                        6524928 =>

               3564 powershell                     61214720 <=

                348 svchost                        62160896 <=

               2904 unsecapp                        6508544 <=

Azaz ezzel a kapcsolóval minden kimeneti objektum megőrizte eredeti mivoltát, csak egy plusz SideIndicator tulajdonságot kapott.

Ha precízebb akarok lenni, akkor a compare-object első két paraméterének neve ReferenceObject és DifferenceObject:

PS C:\> $a = Get-ChildItem -Path C:\PowerShell\Egy

PS C:\> $b = Get-ChildItem -Path C:\PowerShell\Kettő

PS C:\> Compare-Object -ReferenceObject $a -DifferenceObject $b -PassThru | For

mat-Table -Property directoryname, name, sideindicator

 

DirectoryName       Name                   SideIndicator

-------------       ----                   -------------

C:\PowerShell\Kettő Advent2018_16input.ps1 =>

C:\PowerShell\Kettő Advent2018_18.ps1      =>

C:\PowerShell\Kettő LinkedList.ps1         =>

C:\PowerShell\Kettő test-linkedlist.ps1    =>

C:\PowerShell\Egy   Advent2018_13.ps1      <=

C:\PowerShell\Egy   Advent2018_3.ps1       <=

C:\PowerShell\Egy   Advent2018_3_test.ps1  <=

Ha felcserélem a két paraméter értékét attól még az eredmény ugyanaz marad alaphelyzetben, kivéve az eredményelemek sorrendjét:

PS C:\> Compare-Object -ReferenceObject $b -DifferenceObject $a -PassThru | For

mat-Table -Property directoryname, name, sideindicator

 

DirectoryName       Name                   SideIndicator

-------------       ----                   -------------

C:\PowerShell\Egy   Advent2018_13.ps1      =>

C:\PowerShell\Egy   Advent2018_3.ps1       =>

C:\PowerShell\Egy   Advent2018_3_test.ps1  =>

C:\PowerShell\Kettő Advent2018_16input.ps1 <=

C:\PowerShell\Kettő Advent2018_18.ps1      <=

C:\PowerShell\Kettő LinkedList.ps1         <=

C:\PowerShell\Kettő test-linkedlist.ps1    <=

Felmerülhet a kérdés, hogy miért ez a neve ezeknek a paramétereknek, miért nem egyszerűen ObjectA és ObjectB? A paramétereknek igazából akkor van jelentőségük, ha nem a különbségeket, hanem pont az egyezőségeket vizsgáljuk:

PS C:\> Compare-Object -ReferenceObject $a -DifferenceObject $b -PassThru -Incl

udeEqual -ExcludeDifferent | Format-Table -Property directoryname, name, sidein

dicator

 

DirectoryName     Name               SideIndicator

-------------     ----               -------------

C:\PowerShell\Egy Advent2018_13b.ps1 ==

C:\PowerShell\Egy Advent2018_16.ps1  ==

C:\PowerShell\Egy Advent2018_16b.ps1 ==

A fenti példában, ha a referencia objektum az $a, akkor az egyforma objektumok ebből kerültek ki, ha megcseréljük, és a referecia a $b, akkor azok lesznek az eredményhalmaz részei:

PS C:\> Compare-Object -ReferenceObject $b -DifferenceObject $a -PassThru -Incl

udeEqual -ExcludeDifferent | Format-Table -Property directoryname, name, sidein

dicator

 

DirectoryName       Name               SideIndicator

-------------       ----               -------------

C:\PowerShell\Kettő Advent2018_13b.ps1 ==

C:\PowerShell\Kettő Advent2018_16.ps1  ==

C:\PowerShell\Kettő Advent2018_16b.ps1 ==

Így például ha ki szeretnénk törölni az Egy könyvtárból azokat a fájlokat, amik a Kettőben is megvannak, akkor az Egy kell legyen a referencia objektum és a Kettő a differencia:

PS C:\> Compare-Object -ReferenceObject $a -DifferenceObject $b -PassThru -Inc

ludeEqual -ExcludeDifferent | Remove-Item

Ha megnézzük mi maradt, akkor láthatjuk, hogy tényleg az Egy elemei tűntek el:

PS C:\> $a = Get-ChildItem -Path C:\PowerShell\Egy

PS C:\> $b = Get-ChildItem -Path C:\PowerShell\Kettő

PS C:\> Compare-Object -ReferenceObject $a -DifferenceObject $b -PassThru -Incl

udeEqual | Format-Table -Property directoryname, name, sideindicator

 

DirectoryName       Name                   SideIndicator

-------------       ----                   -------------

C:\PowerShell\Kettő Advent2018_13b.ps1     =>

C:\PowerShell\Kettő Advent2018_16.ps1      =>

C:\PowerShell\Kettő Advent2018_16b.ps1     =>

C:\PowerShell\Kettő Advent2018_16input.ps1 =>

C:\PowerShell\Kettő Advent2018_18.ps1      =>

C:\PowerShell\Kettő LinkedList.ps1         =>

C:\PowerShell\Kettő test-linkedlist.ps1    =>

C:\PowerShell\Egy   Advent2018_13.ps1      <=

C:\PowerShell\Egy   Advent2018_3.ps1       <=

C:\PowerShell\Egy   Advent2018_3_test.ps1  <=

Megjegyzés

Nem tökéletes a Compare-Object szerintem. Sajnos, ha vagy a -ReferenceObject, vagy a ‑DifferenceObject $null, akkor hibát kapok, holott simán kaphatnám a másik gyűjtemény összes elemét különbségként:

PS C:\> Compare-Object -ReferenceObject 1,2,3,4  -DifferenceObject $null

Compare-Object : Cannot bind argument to parameter 'DifferenceObject' because

it is null.

At line:1 char:60

+ Compare-Object -ReferenceObject 1,2,3,4  -DifferenceObject $null

+                                                            ~~~~~

    + CategoryInfo          : InvalidData: (:) [Compare-Object], ParameterBin

   dingValidationException

    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,

   Microsoft.PowerShell.Commands.CompareObjectCommand

Erre megoldást a 2.5.7 Meglevő cmdletek kiegészítése, átalakítása fejezetben tárgyal módon viszonylag egyszerűen lehet:

function Compare-Object2{

[CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=113286', RemotingCapability='None')]

param(

    [Parameter(Mandatory=$true, Position=0)]

    [AllowNull()]

    [AllowEmptyCollection()]

    [psobject[]]

    ${ReferenceObject},

 

    [Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]

    [AllowNull()]

    [AllowEmptyCollection()]

    [psobject[]]

    ${DifferenceObject},

 

    [ValidateRange(0, 2147483647)]

    [int]

    ${SyncWindow},

 

    [System.Object[]]

    ${Property},

 

    [switch]

    ${ExcludeDifferent},

 

    [switch]

    ${IncludeEqual},

 

    [switch]

    ${PassThru},

 

    [string]

    ${Culture},

 

    [switch]

    ${CaseSensitive})

 

begin

{

    if($null -eq $ReferenceObject){

        $PSBoundParameters.ReferenceObject = @()

    }

    if($null -eq $DifferenceObject){

        $PSBoundParameters.DifferenceObject = @()

    }

    try {

        $outBuffer = $null

        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))

        {

            $PSBoundParameters['OutBuffer'] = 1

        }

        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Compare-Object', [System.Management.Automation.CommandTypes]::Cmdlet)

        $scriptCmd = {& $wrappedCmd @PSBoundParameters }

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)

        $steppablePipeline.Begin($PSCmdlet)

    } catch {

        throw

    }

}

 

process

{

    try {

        $steppablePipeline.Process($_)

    } catch {

        throw

    }

}

 

end

{

    try {

        $steppablePipeline.End()

    } catch {

        throw

    }

}

<#

 

.ForwardHelpTargetName Microsoft.PowerShell.Utility\Compare-Object

.ForwardHelpCategory Cmdlet

 

#>

 

}

Vastagon kiemeltem azokat a sorokat, amiket ténylegesen én írtam bele.

Ezzel már jól működik:

PS C:\> Compare-Object2 -ReferenceObject 1,2,3,4  -DifferenceObject $null

 

InputObject SideIndicator

----------- -------------

          1 <=

          2 <=

          3 <=

          4 <=



Word To HTML Converter