A Windows PowerShell rejtelmei - 2. rész
A Windows PowerShell használatát bemutató cikksorozat második részében szöveges állományok feldolgozásához adunk tippeket.
A cikksorozat első részében egy egyszerű fájlrendszerező példán keresztül kezdtem bemutatni a PowerShellt. A cikkhez adott hozzászólásokra reagálva fontosnak tartom hangsúlyozni, hogy nem akarom a PowerShellt más shellekkel szembeállítani. Egyrészt azért nem, mert nem értek más shellekhez, így nem tartanám ezt korrektnek.
Másrészt egy ilyen összehasonlításnak annyi értelme lenne, mint például a magyar és az angol nyelv összehasonlításának. Melyik nyelv jobb, szebb, praktikusabb? Ez elég szubjektív dolog, természetesen a legtöbb ember az anyanyelvét tartja a legjobbnak, legszebbnek, nem utolsósorban azért, mert azt tudja legjobban, azt tanulta meg legelőször. Másrészt nyilván Angliában praktikusabb az angol, mint a magyar. Ugyanez a helyzet a PowerShellel is: a Windows platformon valószínű jobb a PowerShell a többi shellnél. Ha csak azt nézzük, hogy számos Microsoft termék PowerShellel kezelhető, más shellel nem, akkor szerintem ez elég egyértelmű.
De térjünk vissza a PowerShellhez! Ebben a példában már próbálok kevésbé szájbarágós lenni, feltételezem immár, hogy az olvasók zöme azért valamilyen programozási ismerettel rendelkezik. A mostani példáimban már a könyvemben is alkalmazott jelölést használom, azaz itt nem a grafikus PowerShell ISE felületen dolgozom, hanem a karakteres konzolon. Kicsit még módosítottam a \"gyári\" prompton, a PS C:\\> elé bebiggyesztettem egy szögletes zárójelek közti sorszámot, hogy jobban lehessen hivatkozni ezekre a sorokra.
Nézzük akkor a következő témakört, a szövegfájlban tárolt adatok feldolgozását! Vegyünk egy képzeletbeli kerékpáros versenyt, amelyen három kategóriában lehet indulni: férfi, nő, ifjúsági, ezek különböző távokat jelentenek. A verseny eredményét egy egyszerű szöveges fájlba írjuk be, legyen a fájl neve bringa.txt:
Név, Nem, Életkor, Táv, Idő Brin Gáspár, férfi, 15, 45, 1:34:35 Soós Tibor, férfi, 39, 60, 2:10:34 Fájdalom Csilla, nő, 22, 30, 1:21:11 Vegetári János, férfi, 35, 60, 1:59:54 Beléd Márton, férfi, 42, 60, 2:22:10 Jövő Éva, nő, 32, 30, 1:12:05 Brill Antal, férfi, 16, 45, 1:54:22 Kerék Pál, férfi, 17, 45, 1:39:54 Hajt Anna, nő, 26, 30, 1:19:32
Ezeket az adatokat kellene feldolgozni PowerShell segítségével. Ha megfigyeljük a szöveget, látható, hogy ez valójában egy CSV (comma separated values) adatokat tartalmazó fájl, azaz vesszővel elválasztott értékek táblázata, az első sorban az „oszlopok” nevei találhatók. Az ilyen formátumú fájlt a PowerShell nagyon könnyen tudja kezelni:
[4] PS C:\\scripts> Import-Csv .\\scripts\\bringa.txt | Format-Table Név Nem Életkor Táv Idő - - - - - Brin Gáspár férfi 15 45 1:34:35 Soós Tibor férfi 39 60 2:10:34 Fájdalom Csilla nő 22 30 1:21:11 Vegetári János férfi 35 60 1:59:54 Beléd Márton férfi 42 60 2:22:10 Jövő Éva nő 32 30 1:12:05 Brill Antal férfi 16 45 1:54:22 Kerék Pál férfi 17 45 1:39:54 Hajt Anna nő 26 30 1:19:32
Látható, hogy CSV fájlok beolvasására az Import-CSV cmdlet áll rendelkezésünkre. A PowerShell terminológiában az \"igazi\" parancsok neve a cmdlet (ejtsd: kommandlet). Ezek olyan parancsok, amelyeket a PowerShell saját infrastruktúrája biztosít, nem pedig valamilyen külső program. Szigorú szabályok vannak a parancsok elnevezésére, mindannyian ige-kötőjel-főnév szerkezetűek, ráadásul a lehetséges igék is jól behatároltak, így sok meglepetésre nem kell számítanunk, ráadásul elég könnyen kitalálható, akár egy számunkra ismeretlen parancsról is, hogy az mit is csinálhat.
Az Import-CSV például beolvassa egy CSV fájl tartalmát és az ottani információk alapján létrehoz speciális objektumokat olyan tulajdonságnevekkel, mint ami a fájl első sorában levő elnevezések. A fenti kifejezésem végén én még táblázatos nézetben kértem az adatokat a Format-Table cmdlet segítségével. Nézzük, hogy a megjelenített sorok tényleg objektumok, azaz tulajdonságaik külön-külön megszólíthatók:
[6] PS C:\\> $versenyzők = Import-Csv .\\scripts\\bringa.txt [7] PS C:\\> $versenyzők[1].idő 2:10:34
Az egyszerűség kedvéért az előbb látott objektum-halmazt betöltöm egy $versenyzők változóba [6] és például Soós Tibor időeredményét kiolvastam [7]. A $versenyzők tömbjének egyes elemeire szögletes zárójelbe írt számmal kell hivatkozni, az első elem a 0. elem, így az én adatsorom az 1. elemben van, és ennek az Idő tulajdonsága adta meg az időeredményemet. Ezzel már most sok minden el tudunk végezni, például keressük a kategóriánkénti győzteseket:
[8] PS C:\\> $versenyzők | Group-Object -Property táv | ForEach-Object {$_.group | Sort-Object -Property idő | Select-Object -First 1} | Format-Table Név Nem Életkor Táv Idő - - - - - Brin Gáspár férfi 15 45 1:34:35 Vegetári János férfi 35 60 1:59:54 Jövő Éva nő 32 30 1:12:05
Mi történt itt? Vettem a versenyzői adatokat és rátettem őket a futószalagra. Az első gép, a Group-Object csoportosította ezeket, \"bedobozolta\" a Táv tulajdonság alapján, ez adja meg az egyes kategóriákat. Ennek kimenete olyan csoportobjektumok halmaza, melyek Group nevű tulajdonságán keresztül érhetők el az adott csoportba tartozó versenyzői adatok, azaz a doboz tartalma. Pillantsunk bele, hogy a gépsor ezen pontján mi is van a futószalagon, hogyan is néznek ki az így képzett dobozok:
[9] PS C:\\> $versenyzők | Group-Object -Property táv Count Name Group - - - 3 45 {@{Név=Brin Gáspár; Nem=férfi; Életkor=15; . 3 60 {@{Név=Soós Tibor; Nem=férfi; Életkor=39; T. 3 30 {@{Név=Fájdalom Csilla; Nem=nő; Életkor=22;.
Látható, hogy három doboz készült, egy-egy dobozon, azaz csoporton belül az oda tartozó adatok érkezési sorrendben vannak, nekem pedig időre kellene rendezni ezeket. Így a [8]-as sorban látható kifejezésben veszem a csoport-objektumokat egyesével a ForEach-Object cmdlettel, és azt adom neki utasításba, hogy vegye az aktuális csoport elemeit, amelyek tehát a Group tulajdonságon keresztül érhetők el. Ezeket helyezze egy belső, újabb futószalagra, amin rendezze idő szerint az adatokat a Sort-Object cmdlet segítségével, majd egy-egy csoportból vegye csak az első elemet, azaz a legkisebb időeredménnyel rendelkező versenyzőt a Select-Object cmdlettel. A legvégén a táblázatos nézetet kiadó Format-Table van ismét.
Fokozzuk ezt! Mi van akkor, ha arra vagyok kíváncsi, hogy vajon mi volt a versenyzők átlagsebessége? Ezt a legelegánsabban úgy tudjuk megtudni, ha ezt egy új tulajdonságként hozzátesszük az egyes versenyzői adatsorokhoz. És ha már alakítgatunk, akkor legyünk még precízebbek! Valójában itt az időadatokat szövegként olvasta be a PowerShell, készítsünk ezekből igazi, idő típusú adatokat. Ezt már a grafikus szkript-szerkesztőben raktam össze, hogy szebb tagolással tudjam ide másolni. Ezeket a formázott, tördelt sorokat egy az egyben ki lehet innen másolni, és be lehet illeszteni közvetlenül a PowerShell konzolba, a sortöréseket és az átlógó sorokat teljesen korrektül kezeli a rendszer:
$versenyzők = @() Import-Csv c:\\scripts\\bringa.txt | ForEach-Object { $_.Idő = [timespan] ($_.idő+\".0\") $_ | Add-Member -Name Átlagsebesség -MemberType NoteProperty -Value ` ($_.táv/($_.idő.totalhours)) $versenyzők += $_ }
Mi történt itt? Előre előkészítettem egy $versenyzők nevű üres tömböt (@() – ez a jelölése egy üres tömbnek), majd a már látott módon elkezdem beimportálni az adatokat. Minden egyes adatsorra átalakítom az Idő tulajdonságot \"igazi\" idővé, ehhez a [timespan] típusjelölőt használtam, de mivel ez számítana még tizedmásodpercekre is, ezért azt még hozzábiggyesztem a beolvasott adathoz. Ezután az aktuális elemet ($_) kibővítettem egy újabb tulajdonsággal, aminek neve Átlagsebesség, típusa un. Noteproperty, ami gyakorlatilag azt jelenti, hogy ez egy statikus adat, értéke pedig a Táv elosztva az előbb kiszámolt Idő órákban kifejezett (totalhours) értékével.
Itt mutatkozik meg az időre alakítás haszna és a PowerShell típusossága, mert látható, hogy milyen egyszerűen lehet ezzel az időtartamok különböző mértékegységekben vett értékét képezni. A legvégén az adott adatsort beraktam a $versenyzők tömb következő elemeként. Nézzük, hogy tényleg benne vannak-e az átlagsebességek, sőt, rendezzük mindjárt eszerint csökkenő sorrendbe:
[26] PS C:\\> $versenyzők | Sort-Object -Property Átlagsebesség -Descending | Format-Table név, táv, idő, átlagsebesség Név Táv Idő Átlagsebesség - - - - Vegetári János 60 01:59:54 30,02502085070 Brin Gáspár 45 01:34:35 28,54625550660 Soós Tibor 60 02:10:34 27,57212152157 Kerék Pál 45 01:39:54 27,0270270270 Beléd Márton 60 02:22:10 25,32239155920 Jövő Éva 30 01:12:05 24,9710982658 Brill Antal 45 01:54:22 23,60827747012 Hajt Anna 30 01:19:32 22,63202011735 Fájdalom Csilla 30 01:21:11 22,17203859577
Hurrá! Benne van. A szebb megjelenítés miatt és most itt csak négy tulajdonságot választottam ki a táblázatba. És még mindig lehet fokozni! Hogyan lehetne például kiszámolni, hogy a versenyzők összesen hány kilométert tekertek, mi volt az átlagos hossz, legkisebb és legnagyobb táv és hány versenyző volt:
[30] PS C:\\> $versenyzők | Measure-Object -Sum -Average -Maximum -Minimum Property táv Count : 9 Average : 45 Sum : 405 Maximum : 60 Minimum : 30 Property : Táv
Hát ez nem volt túl bonyolult! Miután ilyen jellegű egyszerű statisztikák nagyon gyakoriak, ezért a PowerShell alkotói adtak egy beépített cmdletet, a Measure-Object-et, ami megadott tulajdonságra ezeket a statisztikai adatokat kiszámolja. Látható például, hogy 9 versenyzőm volt, összesen 405 km-t tekertek és az átlagtáv pont 45 km volt.
Továbbra is várom a visszajelzéseket, javaslatokat, kérdéseket. A következő részben várhatóan a még egy picit a szövegek feldolgozásával fogok foglalkozni, de a szöveget már nem én fogom begépelni, hanem a webről fogom \"ellopni\".
Soós Tibor (PowerShell MVP, MCT), IQSOFT – John Bryce Oktatóközpont