Keresés szövegfájlokban (Select-String)

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.



Word To HTML Converter