:

Szerző: Soós Tibor

2010. április 13. 09:48

A Windows PowerShell rejtelmei - 3. rész

Harmadik részéhez érkezett a Microsoft Windows parancssorát bemutató cikksorozat. Ebben olyan megoldási ötletek találhatók, amelyek a rendszergazdák hétköznapi üzemeltetési feladataiban is jól hasznosíthatók, például objektumok összehasonlítása vagy adatok letöltése az internetről.

A PowerShellt bemutató cikksorozat harmadik részében az összehasonlítással fogok foglalkozni. Korábban már volt róla szó, hogy a PowerShellben objektumokkal dolgozunk, ráadásul általában nem is egy-egy objektummal, hanem egyszerre többel, ún. objektumgyűjteményekkel. Az első cikkben sok képfájl-objektumot dolgoztam fel, a másodikban sok szövegsor-objektumot. Nagyon sok olyan probléma van, melyek megoldásához ilyen objektumgyűjtemények összehasonlítása révén juthatunk el. Most egy ilyen feladatot fogok itt megoldani a PowerShell segítségével, lottótippeket értékelek ki. A következő cikkben már olyan példákat hozok az összehasonlításokra, melyek a rendszergazdáknak jelenthetnek segítséget: két egyformának hitt számítógép szoftveres különbségeit derítem fel.

Letölthető PowerShell-könyv és vetélkedő

Mielőtt belekezdenék a cikk témájába pár szolgálati közleményt engedjenek meg! Az online médiumok közül elsőként a HWSW olvasói értesülhetnek arról, hogy már szabadon letölthető a PowerShell 2.0 – elmélet és gyakorlat rendszergazdáknak című könyvem a Microsoft Magyarország jóvoltából. Második hír, hogy nemsokára indul a Microsoft Scripting Games 2010 vetélkedője, lesz kezdő és haladó szekció is, így bárki bátran indulhat. Ehhez \"edzőtábort\" is szerveztem, tíz gyakorló feladatot lehet itt találni. Ha ezek közül megoldanak feladatokat és a megoldásokat eljuttatják hozzám, akkor értékelem azokat és majd a \"hivatalos\" megoldásokat is közzéteszem. A legtöbb, legszebb megoldást beküldőnek Microsoft Press szakkönyv a nyereménye. Biztatok mindenkit, aki érdeklődik a PowerShell iránt, hogy vegyen részt az edzőtáborban, nézze meg a korábbi évek feladatait és megoldásait és induljon az idei Scripting Games bajnokságon, nagyon sokat lehet tanulni és ott jóval értékesebb díjak is várnak a nyertesekre!

Lottózzunk a PowerShell segítségével!

Néha veszek lottószelvényt és ha már erre vetemedek, akkor nem is egy számsorral, hanem 3-4-el játszom. Viszont a kiértékelés nem olyan egyszerű, mert a kihúzott számokat szépen egyesével össze kell hasonlítani a tippek számsoraival, ami elég sok összehasonlítgatást igényel. Hogyan tudna ebben a PowerShell segíteni? Vegyünk először csak egy tippsort és a ténylegesen húzott számokat, és hasonlítsuk össze ezeket az objektumgyűjtemények összehasonlítására szolgáló Compare-Object cmdlet segítségével:

[1] PS C:\\> Compare-Object 28,38,55,68,82  10,20,30,40,55

                            InputObject SideIndicator
                            - -
                                     10 =>
                                     20 =>
                                     30 =>
                                     40 =>
                                     28 <=
                                     38 <=
                                     68 <=
                                     82 <=

 

A Compare-Object cmdletnek átadok két számsort összehasonlításra, az első a múlt heti nyertes számokat tartalmazza, a második a tippemet. A két számsor között szóköz van, ezzel lehet a PowerShellben a cmdletek paramétereit elválasztani, mert – ahogy az itt is látható – a vessző a tömbelemek (gyűjteményelemek) elválasztására van fenntartva. Az eredmény \"grafikusan\" jelzi, hogy mik az eltérések. Ezek között a számok között csak az 55 közös, az nem szerepel a listán, a 10 csak a második kupacban van, így mellette egy \"=>\" nyíl található, a 28 meg csak az első kupacban van, az ő nyila meg fordított \"<=\".

Ez mind szép, de nekem pont az kellene, hogy melyek az egyforma számok. Szerencsére a Compare-Object működése szabályozható. Egy újabb paraméterrel kérhetem, hogy a különbözőségeket ne mutassa, egy másikkal meg azt, hogy az egyezőségeket viszont jelenítse meg:

 

[2] PS C:\\> Compare-Object 28,38,55,68,82  10,20,30,40,55 -ExcludeDifferent  
-IncludeEqual InputObject SideIndicator - - 55 ==

 

Ha csak azt szeretném látni, hogy mik az eltalált számok, arra kicsit kell csak módosítani a fenti kifejezést:

 

[3] PS C:\\> Compare-Object 28,38,55,68,82  10,20,30,40,55  
-ExcludeDifferent -IncludeEqual | ForEach-Object {$_.inputobject} 55

 

Ha meg arra vagyok kíváncsi, hogy hány találatom van, akkor ennek a „számosság” tulajdonságát kell lekérnem:

 

[4] PS C:\\> (Compare-Object 28,38,55,68,82  10,20,30,40,55 
-ExcludeDifferent -IncludeEqual | ForEach-Object {$_.inputobject}).count [5] PS C:\\>

 

Hoppá! Ez nem adott semmit. Azért, mert itt csak egy találatom volt. Ha kettő lett volna, akkor működött volna:

 

[5] PS C:\\> (Compare-Object 28,38,55,68,82  10,20,30,68,55  
-ExcludeDifferent -IncludeEqual | ForEach-Object {$_.inputobject}).count 2

 

Vajon miért? Mert az egész összehasonlításnak a kimenete is objektum, csakhogy ez egy találat esetében maga az egy darab szám, és a számnak nincs \"count\" tulajdonsága. Míg ha több találatom volt, akkor a kimenet egy gyűjtemény, annak már van \"count” tulajdonsága. De nem baj, szerencsére van egy olyan jelölés a PowerShellben, ami garantálja, hogy mindig gyűjtemény legyen a kimenet, még akkor is, ha csak egy elem van benne. Ezt egy @() zárójelezéssel tudjuk elérni:

 

[6] PS C:\\> @(Compare-Object 28,38,55,68,82  10,20,30,40,55  
-ExcludeDifferent -IncludeEqual | ForEach-Object {$_.inputobject}).count 1

 

És természetesen ez két találatnál is továbbra is tökéletesen működik:

 

[7] PS C:\\> @(Compare-Object 28,38,55,68,82  10,20,30,68,55  
-ExcludeDifferent -IncludeEqual | ForEach-Object {$_.inputobject}).count 2

 

Ennek birtokában készíthetünk egy saját PowerShell cmdlet-szerűséget, aminek paraméterként átadva két számsort kiírja, hogy hány egyező szám van köztük és hogy melyek ezek. Ezt függvénydefiniálással oldhatjuk meg. Most ide a grafikus szkriptszerkesztőből van másolva, de egy az egyben bemásolható a konzolablakba is és végső Enter leütésével létrejön a függvénydefiníció:

 

function ellenőriz-tipp {
param (
	[int[]] $húzottak,
	[int[]] $tippek)
$találat = @(
	Compare-Object $húzottak $tippek -IncludeEqual -ExcludeDifferent | 
		ForEach-Object {$_.inputobject}) 
Write-Host \"$tippek : \" -NoNewline
if($találat) {
	write-host \"Találat: $($találat.count), eltalált számok: $találat\"
}
else {write-host \"Nincs találat\"}
}

 

A függvény neve az, hogy \"ellenőriz-tipp\". Két paramétert vár, az első a \"húzottak\", a második a \"tippek\". Mindkettő egész számok gyűjteménye. A függvény belsejében az előbb kitalált módon a $találat változóba teszem az eltalált számokat, majd kiírom a képernyőre először a tippként megadott számokat, majd aszerint, hogy volt-e találat, még mellé írom, hogy hány találat volt és melyek azok. Különben meg azt írom ki, hogy \"Nincs találat\". Nézzük, hogyan működik ez:

 

[9] PS C:\\> ellenőriz-tipp 28,38,55,68,82  10,20,30,40,55
10 20 30 40 55 : Találat: 1, eltalált számok: 55
[10] PS C:\\> ellenőriz-tipp 28,38,55,68,82  10,20,30,68,55
10 20 30 68 55 : Találat: 2, eltalált számok: 68 55
[11] PS C:\\> ellenőriz-tipp 28,38,55,68,82  10,20,30,40,50
10 20 30 40 50 : Nincs találat

 

Irány az internet!

Mi van azokkal a játékfüggőkkel, akik több tucat számsorral játszanak? Ők az egyszerűség kedvéért egy szöveges fájlba beírhatják a tippsoraikat és a PowerShell onnan is be tudja olvasni azokat, és automatikusan kiértékeli az összes tippsort. De csavarjunk még egyet a dolgon! Én már a húzott számokat is lusta vagyok begépelni, szedje össze ezeket a PowerShell az internetről! Sajnos a \"hivatalos\" lottó oldalon nagyon bonyolultan vannak fent a nyerőszámok, de a Teletipp weboldalról könnyebben le lehet szedni ezeket. Az ezt elvégző függvény így néz ki:

 

function get-lotto {
$wc = New-Object net.WebClient
$oldal = $wc.DownloadString(\"http://www.teletipp.hu/\")
$keres1 = \'5/90\\sötöslottó \\r\\n\'
$keres2 = \'(\\d+\\.\\sjátékhét\\s\\d{4}\\.\\d{2}\\.\\d{2}\\.)\'
$keres3 = \'\\r\\n\'
$keres4 = \'(\\d{1,2},\\d{1,2},\\d{1,2},\\d{1,2},\\d{1,2})\'
if($oldal -match ($keres1 + $keres2 + $kerese3 + $keres4))
{
Write-host $Matches[1]
$Matches[2] -split \",\"
}
}

 

Ne ijedjen meg senki, nem olyan bonyolult, mint amilyennek látszik. Ennek a get-lotto függvénynek nincs is paramétere. A webes elérés nagyon egyszerű, a PowerShell alatt ott a .NET keretrendszer és abban már vár ránk egy olyan osztály, ami egyszerű webböngészést végez. Ez az osztály a System.Net.WebClient névre hallgat, a PowerShellben nem kell kiírni a System előtagot. Ennek az osztálynak egy objektumát hozom létre a $wc változóba a New-Object cmdlet segítségével, és ennek a DownloadString metódusával töltöm le a Teletipp oldalát. Ez egy nagy, többsoros szöveget ad, a weboldal HTML forráskódját, ebben kell csak megkeresni az ötöslottó számait tartalmazó részt.Hogy hogyan, azt az \"igazi\" böngészőnk forráskód megjelenítőjének segítségével tudjuk megalkotni:

\"\"

A kiemelt rész kell nekünk. Ezt időtálló módon egy regex kifejezés segítségével tudjuk megtalálni. A kereső kifejezést négy részből, a $keres1 - $keres4-ből raktam össze, de csak azért, hogy kényelmesen kiférjen a képernyőre. A lényege az, hogy keresem azt a szöveget, hogy \"5/90 ötöslottó...\" ( a szóközt \\s-sel, a sortörést \\r\\n-nel jelöljük regexben). Ezt még könnyű megtalálni, de ezután jön egy olyan rész, ami hétről-hétre más. Ez szerencsére a regexnek nem okoz gondot! A $keres2-ben azt fogalmazom meg, hogy keresek legalább egy számjegyet (\\d+), utána legyen egy szóköz, majd az, hogy \"játékhét\", majd megint csak 4 és 2 számjegyekből, valamint vesszőkből álló dátum. A nyerőszámok meg a $keres4 kifejezéssel kereshetők, szintén számjegyek és vesszők formájában. (Bővebben a könyvemben.)

A nekem kellő részeket, azaz a játékhét adatait és magukat a nyerőszámokat zárójelezéssel kiemelem, így majd a végső találatból ezekre külön is hivatkozhatok. Az $oldal változóban található HTML forrásban megkeresem tehát az előbb összerakott kereső kifejezéssel a nekem érdekes részt, erre a PowerShellben a match operátor áll rendelkezésünkre. Ha megtalálta a teljes keresett kifejezést, akkor a találat a $Matches automatikus változóba kerül. Ez valójában egy gyűjtemény-szerűség, ennek [0]-ás eleme lenne a teljes találat (lásd a képernyőkép kiemelt részét), de ez nekem pont nem kell. Ami kell, az ennek [1]-es eleme, a játékhét adatai (az első bezárójelezett rész, azaz a $keres2-re illeszkedő szöveg), amit ki is írok a konzolra, valamint a [2]-es eleme, maguk a nyerőszámok ($keres4-nek megfelelő rész), ez lesz a függvény visszatérési értéke, ezt csak \"simán\" kiadom. Nézzük, hogyan működik ez:

 

[16] PS C:\\> get-lotto
13. játékhét 2010.04.03.
28
38
55
68
82

 

Most már csak össze kell hozni a két függvényt és a fájlba lerakott tippjeimet fel kell olvasni:

 

[20] PS C:\\> $húzás = get-lotto
13. játékhét 2010.04.03.
[21] PS C:\\> $húzás
28
38
55
68
82
[22] PS C:\\> Get-Content c:\\_munka\\tippek.txt | ForEach-Object 
{ellenőriz-tipp $húzás ($_ -split \",\")} 11 27 34 53 87 : Nincs találat 5 10 14 39 70 : Nincs találat 1 26 42 68 82 : Találat: 2, eltalált számok: 68 82

 

A [20]-as sorban a $húzás változóba betöltöm az aktuális nyerőszámokat. Amikor kiírom a $húzás változót látható, hogy maga a címke, azaz hogy \"13. játékhét...\" nem került bele, mert a függvényben az nem visszatérési érték volt, csak képernyőre kiíratás. Az utolsó kifejezés a [22]-es sorban remélem már érthető! Beolvasom a tippek.txt fájlt, amiben soronként vesszővel elválasztva vannak a tippek. Ezekkel a tippsorokkal meghívom az \"ellenőriz-tipp\" függvényemet, aminek első paramétere a $húzás változó, amibe előbb töltöttem be a nyerőszámokat, második paraméterét, a tippjeimet meg a szövegfájl aktuális sorának vesszők mentén történő feldarabolása után kapom (-split operátor). A kimenetben látszik, hogy két tippsoromban nem volt találat, egy tippsoromban meg két találat volt. (Sajnos ezt is csak a példa kedvéért \"hamisítottam\", valójában nem volt egy találatom sem.)

A cikksorozat következő részeiben immár egyre rendszergazda-közelibb témákat veszek elő. Addig is, buzdítok mindenkit a Scripting Games vetélkedőn és az edzőtáboron való részvételre! Ha valaki valahol elakad és segítségre van szüksége, szívesen segítek.

Soós Tibor (PowerShell MVP, MCT), IQSOFT – John Bryce Oktatóközpont

a címlapról