Például keressük meg a Windows mappában azokat a naplófájlokat, amelyekben szerepel az „error” kifejezés. A listában szerepeljen a fájl neve, a megtalált sor szövege, és hogy az hányadik sor az adott fájlon belül! Kezdjünk hozzá! Az első megoldásban tulajdonképpen semmi különleges nincsen, a Get-Content sorban megnyitja valamennyi logfájlt, az eredményt odaadjuk a Select-Stringnek, aki kiválogatja a megfelelő sorokat. Kell még egy kis formázgatás és készen is vagyunk.
PS C:\> Get-Content $env:windir\*.log | select-string -pattern "error" | f
ormat-list filename,line,linenumber
Filename : InputStream
Line : COM+[7:32:26]: Warning: error 0x800704cf in IsWin2001Primary
DomainController
LineNumber : 260
...
A feladatot két különböző módon is meg fogjuk oldani, bár az első próbálkozás közel sem ad majd tökéletes eredményt. Ha megvizsgáljuk a csőben áramló adatok természetét, akkor nyilvánvalóvá válik, hogy ezzel a módszerrel nem is lehetséges a tökéletes megoldás. Sajnos azonban ezzel a módszerrel útközben olyan információt is eldobáltunk, amire feltétlenül szükségünk lenne a helyes eredmény előállításához. Vizsgáljuk meg, milyen kimenetet produkál a fenti esetben a Get-Content! A fájlok tartalma jelenik meg a kimeneten, soronként egy-egy karakterlánc képében, de mindenféle strukturáltság nélkül. A Select-String már semmiféle információt nem kap arról, hogy melyik karakterlánc melyik fájlhoz tartozott eredetileg, és még kevésbé tudhatja azt, hogy hányadik sor volt az a fájlban.
Mi került akkor a Select-String kimenetébe? A fájlnév helyén mindenhol az InputStream kifejezés található, a LineNumber pedig azt mutatja, hogy az adott karakterlánc hányadik volt a teljes bemenetben, vagyis az egymás mögé illesztett naplófájlokban. Hát ez nem az igazi!
A Select-String azonban bemenetként nem csak karakterláncokat, hanem közvetlenül szöveges fájlokat is fogadhat, a következő megoldásban ezt a tulajdonságot fogjuk felhasználni. Ebben az esetben a Get-ChildItem cmdlettől nem a fájlok tartalmát, hanem csak egy FileInfo objektumokból álló gyűjteményt kap a Select-String, a fájlok tartalmát már ő maga fogja kiolvasni.
PS C:\> Get-ChildItem $env:windir\*.log | select-string -pattern "error" -
list | format-list filename,line,linenumber
Filename : comsetup.log
Line : COM+[7:32:26]: Warning: error 0x800704cf in IsWin2001Primary
DomainController
LineNumber : 220
...
Ebben az esetben minden szükséges információ rendelkezésre áll, és helyesen kerül be a Select-String kimenetébe, így helyesen jelenhet meg a táblázatban is. A ‑list paraméter arra utasítja a cmdletet, hogy csak az első találatig olvasson minden egyes fájlt, így a kapott lista már nem lesz olyan hosszú, mint korábban.
A hibák felderítésénél az is fontos, hogy a hibajelzés környékén milyen egyéb üzenetek vannak a naplófájlban. A Select-String ezt is megoldja! Ugyanis van egy –Context paramétere is, amellyel nem csak a mintának megfelelő találati sor jelenik meg, hanem annyi megelőző és utána jövő sor, amennyit megadunk a –Context-nek.
[90] PS C:\> (Select-String -Path C:\Windows\WindowsUpdate.log -Pattern "error"
-Context 1)[0]
Windows\WindowsUpdate.log:504:2009-11-09 22:15:25:564 872 4ec A
U UpdateDownloadProperties: download priority has changed from 3 to 2.
> Windows\WindowsUpdate.log:505:2009-11-09 22:15:25:564 872 4ec A
U WARNING: Failed to change download properties of call, error = 0x80070057
Windows\WindowsUpdate.log:506:2009-11-09 22:15:25:564 872 4ec A
U UpdateDownloadProperties: download priority has changed from 3 to 2.
Most csak az első találatot kértem, az „igazi” találati sor a „>” jellel kezdődő, és előtte és mögötte ott van az előzmény és utózmány is. Nézzük meg ezt a kimenetet részletesebben:
[91] PS C:\> (Select-String -Path C:\Windows\WindowsUpdate.log -Pattern "error"
-Context 1)[0] | fl *
IgnoreCase : True
LineNumber : 505
Line : 2009-11-09 22:15:25:564 872 4ec AU WARNING: Faile
d to change download properties of call, error = 0x80070057
Filename : WindowsUpdate.log
Path : C:\Windows\WindowsUpdate.log
Pattern : error
Context : Microsoft.PowerShell.Commands.MatchInfoContext
Matches : {error}
Látható, hogy a találat a kimenet Line tulajdonságában van, de akkor hol az előzmény és utózmány? Ezek a Context tulajdonságban vannak valójában:
[92] PS C:\> (Select-String -Path C:\Windows\WindowsUpdate.log -Pattern "error"
-Context 1)[0].context
PreContext PostContext DisplayPreContext DisplayPostContext
---------- ----------- ----------------- ------------------
{2009-11-09 2... {2009-11-09 2... {2009-11-09 2... {2009-11-09 ...
Ezen belül is a Context-nek a PreContext és PostContext tulajdonságban van az előzmény és az utózmány.
Ha már szövegvizsgálatnál tartunk, készítsünk statisztikát például az about_signing.help.txt fájl tartalmáról! Kezdjük a legegyszerűbb, már ismert módszerrel, használjuk a Measure-Object cmdletet!
[2] PS C:\> Get-Content $pshome\en-US\about_signing.help.txt | Measure-Object -
line -word -character
Lines Words Characters Property
----- ----- ---------- --------
218 1576 11754
Eddig rendben is van, de mit kell tennünk, ha arra is kíváncsiak vagyunk, hogy egy adott szó hányszor szerepel a fájlban, vagy például arra, hogy melyik szó fordul elő benne a legtöbbször. A Get-Content soronként tördelt kimenetet ad, először is tördeljük ezt tovább szavakká:
[17] PS C:\> $szavak = Get-Content $pshome\en-us\about_signing.help.txt | forea
ch-object {-split $_} | Where-Object {$_}
Azt hiszem, a fenti parancs azért igényelhet némi magyarázatot. Először is készítünk egy tömböt, ami karakterlánc változókat tud majd fogadni, ebbe kell majd beledobálni a szöveg szavait. A Get-Content szállítja a szöveget soronként, a Foreach-Object pedig minden egyes sort szavakká tördel a –split operátor használatával. A –split minden sort a szavaiból álló karakterlánc-tömb képében ad vissza, ezeket adjuk hozzá egyesével a $szavak tömbhöz. A cső vagy futószalag végén a where-object szűrés az üres karakterláncokat dobja el. Nézzük mi lett ebből:
[18] PS C:\> $szavak.length
1576
Remek! A szavak száma pontosan megegyezik azzal, amit a Measure-Object adott vissza, valószínűleg minden rendben van. A statisztika most már nem gond, a Group-Object csoportosít, a Sort-Object pedig az előfordulások száma szerint sorba rendez:
[19] PS C:\> $szavak | group-object | sort-object count -descending
Count Name Group
----- ---- -----
110 the {the, The, The, the, the, the, The, the, th...
52 to {to, to, to, to, TO, TO, to, to, To, To...}
49 you {you, you, you, you, you, you, you, you, yo...
46 a {a, a, a, a, a, a, a, a, a, a...}
34 certificate {certificate, certificate, certificate, cer...
…
Nem probléma az sem, ha egy adott szó előfordulásainak számára vagyunk kíváncsiak, a sorba rendezés helyett egyszerűen a csoportosított listából ki kell választanunk a megfelelő sort. A „scripts” szó előfordulásainak számát például a következő parancs írja ki:
[21] PS C:\> $szavak | group-object | where-object {$_.Name -eq "scripts"}
Count Name Group
----- ---- -----
23 scripts {scripts, scripts, scripts, scripts, script...
Az egyszerűség kedvéért egyetlen sorba is belesűríthetjük a feladat teljes megoldását, ebben az esetben nincs szükség a változóra csak a következő parancsot kell begépelnünk:
[22] PS C:\> Get-Content $pshome\en-us\about_signing.help.txt | foreach-object
{-split $_} | Where-Object {$_} | group-object | sort-object count -descending
Count Name Group
----- ---- -----
110 the {the, The, The, the, the, the, The, the, th...
52 to {to, to, to, to, TO, TO, to, to, To, To...}
49 you {you, you, you, you, you, you, you, you, yo...
46 a {a, a, a, a, a, a, a, a, a, a...}
34 certificate {certificate, certificate, certificate, cer...
…
Megjegyzés
A fejezet elején használtuk ezt a formátumot a windir környezeti változó kiolvasásához:
[84] PS C:\> $env:windir
C:\Windows
Az „env:” egy PSDrive, jön az ötlet, hogy vajon fájlokat meg lehet-e ugyanilyen formában szólítani? Van nekem egy „futók.txt” fájlom a c:\munka könyvtárban:
[87] PS C:\munka> $c:futók.txt
Ez nem eredményezett semmit. Talán a teljes elérési út:
[88] PS C:\munka> $c:\munka\futók.txt
Unexpected token '\munka\futók.txt' in expression or statement.
At line:1 char:20
+ $c:\munka\futók.txt <<<<
+ CategoryInfo : ParserError: (\munka\futók.txt:String) [], Pare
ntContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
Még rosszabb, hiszen a parancsértelmező a vissza perjelet nem nagyon szereti a kifejezésékben. Korábban már láthattuk, hogy ha „zűrös” karakterek vannak a változók neveiben, akkor kapcsos zárójelbe téve a rendszer elfogadja azokat. Próbáljuk ezt itt is ki:
[89] PS C:\munka> ${c:\munka\futók.txt}
Név, Idő
Béla, 5:12
Dezső, 5:37
Karcsi, 5:36
Sikerült! Sőt, ugyanez a teljes elérési út nélkül is működik:
[90] PS C:\munka> ${c:futók.txt}
Név, Idő
Béla, 5:12
Dezső, 5:37
Karcsi, 5:36
Azaz a get-content helyett ezt is használhatjuk. Természetesen a get-content jóval több szolgáltatást nyújt számunkra, így inkább ezt csak érdekességként említettem.