Regex (-match, -replace, -cmatch, -creplace)

A dzsókerek nagyon praktikusak, de egy csomó mindenre nem jók. Például szeretnénk megvizsgálni, hogy egy szöveg e-mail cím formátumú-e? Vagy van-e a szövegben valahol egy webcím, vagy telefonszám? Mivel ilyen jellegű vizsgálatok nagyon gyakoriak a számítástechnikában, ezért erre külön „tudományág” alakult, matematikai alapját pedig a formális nyelvek képezik.

Az ilyen jellegű szövegminta vizsgálatok kifejezéseit hívják Regular Expression -nek, vagy röviden Regex -nek. Miután ez ténylegesen külön tudomány, Regular Expression kifejezésekről számtalan könyv látott napvilágot, ezért itt most nem vállalkozom arra, hogy még csak közelítő részletességgel tárgyaljam ezt a témát, de azért a főbb elveket és jelöléseket megpróbálom bemutatni.

Van-e benne vagy nincs?

Elsőként nézzünk a legegyszerűbb esetet, amikor egy szövegről el akarjuk dönteni, hogy van-e benne általunk keresett mintára hasonlító szövegrész:

[12] PS C:\> "Benne van a NAP szó" -match "nap"

True

[13] PS C:\> "Benne van a NAPfény szó" -match "nap"

True

A fenti két példa még nem igazán mutatja meg a regex erejét, akár a –like operátor is használható lett volna:

[14] PS C:\> "Benne van a NAP szó" -like "*nap*"

True

De vajon mit tennénk, ha a „napfény”-t nem tekintenénk a mintánkkal egyezőnek, csak az önálló „nap”-ot? Az önállóságot persze sokfajta karakter jelölheti: szóköz, pont, zárójel, stb. A regex-szel ez nagyon egyszerű, használni kell a „szóvég” jelölő „\b” karakterosztály szimbólumot a mintában:

[15] PS C:\> "Benne van a NAPfény szó" -match "\bnap\b"

False

[16] PS C:\> "Benne van a NAP. Fény is" -match "\bnap\b"

True

[17] PS C:\> "Benne van a (NAP)" -match "\bnap\b"

True

 Azaz keresek egy olyan mintát, amely áll egy szóelválasztó karakterből, a „nap”-ból, és megint egy szóelválasztó karakterből.

Megjegyzés:

Mint ahogy láttuk, egy reguláris kifejezésben a „\b” szóhatárt jelöl, de ha ezt szögletes zárójelek között „[]” alkalmazzuk (lásd később a karakterosztályokat), akkor a „\b” a visszatörlés, azaz a „backspace” karaktert jelenti. A cserénél (-replace, lásd szintén később) a „\b” mindig visszatörlés karakter jelent.

Vagy keresem, hogy van-e a szövegben szám:

[26] PS C:\> "Ebben van 1 szám" -match "\d"

True

A \d a számjegy jelölője. Ezen jelölőknek van „negáltjuk” is, \B – a nem szóelválasztó karakter, \D – a nem számjegy.

Vagy keresem a Soós nevűeket, akik lehetnek „Sós”-ok is, de azért „Sooós” ne legyen:

[23] PS C:\> "Soós" -match "So?ós"

True

[24] PS C:\> "Sós" -match "So?ós"

True

[25] PS C:\> "Sooós" -match "So?ós"

False

A „?” 0 vagy 1 darabot jelöl az azt megelőző regex kifejezésből. A * bármennyi darabot jelent, akár 0-t is. A + legalább 1 darabot jelöl. Tetszőleges darabszámot jelölhetünk {min,max} formában, ahol a maximum akár el is hagyható.

Kereshetem az „s”-sel kezdődő, „s”-sel végződő háromkarakteres szavakat:

[26] PS C:\> "Ez egy jó találat 'sas' nekem" -match "\bs.s\b"

True

[27] PS C:\> "Ez nem jó találat 'saras' nekem" -match "\bs.s\b"

False

Alaphelyzetben a „.” minden karaktert helyettesít, kivéve a sortörést.

Egyszerűen vizsgálhatom, hogy van-e a szövegben ékezetes betű:

[28] PS C:\> "Ékezet van ebben több is" -match "[áéíóöőüű]"

True

[29] PS C:\> "Ekezetmentes" -match "[áéíóöőüű]"

False

A [] zárójelpár közti betűk vagylagosan kerülnek vizsgálat alá, ezt is úgy hívjuk, hogy karakterosztály (meg a \d, \w, stb. kifejezéseket is). Ezzel karakter tartományokat is meg lehet jelölni:

[30] PS C:\> "A" -match "[a-f]"

True

[31] PS C:\> "G" -match "[a-f]"

False

Természetesen ezeket az alapelemeket kombinálni is lehet. Keresem a legfeljebb négyjegyű hexadecimális számot:

[32] PS C:\> "Ebben van négyjegyű hexa: 12AB" -match "\b[0-9a-f]{1,4}\b"

True

[33] PS C:\> "Ebben nincs: 12kicsiindián" -match "\b[0-9a-f]{1,4}\b"

False

[34] PS C:\> "Ebben sincs: 12baba" -match "\b[0-9a-f]{1,4}\b"

False

A fenti karakterosztályokban használhatunk negált változatot is. Például tartalmaz olyan karaktert, ami nem szóköz jellegű karakter:

[35] PS C:\> "Van nem szóköz is benne" -match "[^\s]"

True

[36] PS C:\> "     " -match "[^\s]"

False

A „nem szóköz”-nek van egyszerűbb jelölése is:

[37] PS C:\> "Van nem szóköz is benne" -match "[\S]"

True

[38] PS C:\> "     " -match "[\S]"

False

Ennek analógiájára van „nem szókarakter” - \W, „nem számjegy” - \D.

A szögletes zárójelek között csak karaktereket használhatok „vagylagos” értelemben. Ha azt akarom vizsgálni, hogy a szövegben vagy „PowerShell” vagy „PS” található, azt így vizsgálhatom:

[39] PS C:\> "Ebben PowerShell van" -match "PowerShell|PS"

True

[40] PS C:\> "Ebben PS van" -match "PowerShell|PS"

True

[41] PS C:\> "Ebben egyik sem" -match "PowerShell|PS"

False

Hogy egy kicsit trükkösebb legyen, nézzük az alábbi két példát:

[42] PS C:\> "ab" -match "[^ab]"

False

[43] PS C:\> "ab" -match "[^a]|[^b]"

True

A [42]-es sorban azért kaptunk hamis eredményt, mert kerestünk egy nem „a”, de nem is „b” karaktert, de az „ab”-ben csak ilyen van, így hamis eredményt kaptam. A [43]-as sorban kerestem egy vagy nem „a”, vagy nem „b” karaktert, márpedig az „ab” első „a”-jára igaz, hogy az nem „b”, tehát van találat!

További karakterosztályok

Láthattuk, hogy szögletes zárójelek között vizsgálhatunk többfajta karakter létét vagy hiányát a mintánkban. Ennek van még továbbfejlesztett módja is. Például tagadást másként is ki lehet fejezni:

[15] PS C:\> "hijk" -match "[a-z-[h-i]]"

True

Itt azt vizsgáltam, hogy a mintában van-e olyan karakter, ami „a” és „z” közötti, kivéve „h” és „i”. Ez itt igaz volt. Ellenben:

[16] PS C:\> "hijk" -match "[a-z-[h-k]]"

False

Ez már hamisra értékelődött. Ezt még mélyebb beágyazással is lehetne bonyolítani:

[23] PS C:\> "h" -match "[a-z-[h-k-[i-j]]]"

False

[24] PS C:\> "i" -match "[a-z-[h-k-[i-j]]]"

True

A fenti bonyolult kifejezést így kell értelmezni: vesszük a teljes angol abc-t, ebből kiszedem a „h” és „k” közötti karaktereket, majd „visszarakom” az „i” és „j” közöttieket.

További lehetőségeink vannak Unicode karakterek esetében, márpedig a PowerShell alaphelyzetben a szövegeket unicode-ként kezeli. Ez pedig a \p{tulajdonság} formátumú hivatkozás:

Tulajdonság

Jelentés

Lu

 Betű, nagybetű

Ll

 Betű, kisbetű

Nd

 Szám, decimális

No

 Szám, egyéb: ² ³ ¹ ¼ ½ ¾

Pd

 Elválasztójel: -

Ps

 Elválasztójel, nyitó: ( [ {

Pe

 Elválasztójel, záró: ) ] }

Pi

 Elválasztójel, nyitó angol idézőjel: «

Pf

 Elválasztójel, záró angol idézőjel: »

Po

 Elválasztójel, egyéb: ! " # % & ' * , . / : ; ? @ \ ¡ · ¿

Sm

 Szimbólum, matematikai: + < = > | ~ ¬ ± × ÷

Sc

 Szimbólum, valuta: $ ¢ £ ¤ ¥

Sk

 Szimbólum, módosító: ^ ` ¨ ¯ ´

So

 Szimbólum, egyéb: ¦ § © ® ° ¶

Zs

 Elválasztó, space

Zl

 Elválasztó, sor

Zp

 Elválasztó, paragrafus

 Ezek vizsgálatához az alábbi kifejezést használtam:

[83] PS C:\> $ofs = ""; $minta = [string] (0..255 | ForEach-Object {[char]$_})

[84] PS C:\> $minta

 ☺☻♥♦

♂♀♫☼►◄↕‼¶§↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXY

Z[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§

¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõ

ö÷øùúûüýþÿ

[85] PS C:\> $m="P"; $reg = [regex] "\p{$m}"; [string] ($reg.Matches($minta) |

Select-Object -Property value | %{[char] $_.value})

!"#%&'()*,-./:;?@[\]_{}¡«­·»¿

[86] PS C:\> $m="Ll"; $reg = [regex] "\p{$m}"; [string] ($reg.Matches($minta) |

 Select-Object -Property value | %{[char] $_.value})

abcdefghijklmnopqrstuvwxyzªµºßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ

A [83]-as sorban létrehoztam egy olyan sztringet, amelyik tartalmazza az összes alap karaktert, majd a [85]-ös sorban készítettem egy olyan regex kifejezést, ami használta ezt a tulajdonság-alapú mintát, és amely visszaadta az összes karaktert egy egyesített sztring formában, amely karakterek kielégítették a regex kifejezést. Látható, hogy a „\p{P}”-re az összes elválasztó karaktert kaptam vissza, a [86]-os sorban a „\p{Ll}” az összes kis betűt.

Van benne, de mi?

Az eddigiekben a –match operátorral arra kaptunk választ egy $true vagy $false formájában, hogy a keresett minta megtalálható-e a szövegünkben vagy sem. De egy bonyolultabb mintánál, ahol „vagy” feltételek vagy karakterosztályok is vannak, egy $true válasz esetében nem is tudjuk, hogy most mit is találtunk meg. Nézzük meg ezt a kettővel előző fejezet [43]-as sorának példája kapcsán:

[43] PS C:\> "ab" -match "[^a]|[^b]"

True

[44] PS C:\> $matches

 

Name                           Value

----                           -----

0                              a

Mint látható, a PowerShell automatikusan generál egy $matches  változót, amely tartalmazza, hogy mit is találtunk a –match operátor alkalmazása során. Jelen esetben megtalálhattuk volna a „b”-t is, hiszen arra is igaz, hogy „nem a”, de a –match operátor szigorúan balról jobbra halad, így először az „a” karakterre vizsgálja a mintát. Ha ott sikert ér el, akkor nem is halad tovább, megelégszik az eredménnyel és abbahagyja a keresgélést.

A $matches igazából nem is egy tömböt, hanem egy hashtáblát tartalmaz, amelynek 0-s kulccsal hivatkozható eleme a megtalált sztring. Nézzünk erre néhány példát:

[22] PS C:\> "Ez itt egy példa: szöveg" -match ":.*"

True

[23] PS C:\> $matches[0]

: szöveg

[24] PS C:\> $matches.gettype()

 

IsPublic IsSerial Name                                     BaseType

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

True     True     Hashtable                                System.Object

Kerestük a kettőspontot és utána tetszőleges számú karaktert, illetve látható, hogy a $matches tényleg hashtábla.

A mohó regex

A –match mögött meghúzódó regex motor „mohó”, azaz ha rá bízzuk a mennyiségeket, akkor ő alapesetben annyit vesz belőle, amennyit csak tud a –match kielégítése mellett. Ez nem biztos, hogy nekünk mindig jó:

[26] PS C:\> "Keresem ezt: xTalálatx, az x-ek közti szöveget" -match "x.*x"

True

[27] PS C:\> $matches[0]

xTalálatx, az x

Mi történt itt? Ugye xTalálatx-re számítottunk, de „mohóság” miatt az első x megtalálása után a „.*” minta a szövegem végéig kielégíti a feltételt. Csak az utolsó karakter elérése után döbben rá a regex motor, hogy lemaradt az utolsó feltétel, a második x teljesítése, és ekkor kezd visszafele lépkedni, hogy hátha még ezt is tudja teljesíteni valahol. A visszafele lépkedés során eljut a harmadik x-ig, és ott áll meg. Mit lehetett volna tenni, hogy a kívánt találatot kapjuk meg? A regex „mohóságát” ki is lehet kapcsolni, ehhez egy „?”-et kell tenni az ilyen meghatározatlan számú többszörözők után. Ekkor csak a minimális darabszámot veszi a mintából, és ha az nem elég, akkor növeli azt:

[32] PS C:\> "Keresem ezt: xTalálatx, az x-ek közti szöveget" -match "x.*?x"

True

[33] PS C:\> $matches[0]

xTalálatx

Vagy pontosíthatjuk a „.*” mintát, hiszen ha például a két x között szókarakterek lehetnek csak, akkor pontosabb ez a minta:

[41] PS C:\> "Keresem ezt: xTalálatx, az x-ek közti szöveget" -match "x\w*x"

True

[42] PS C:\> $matches[0]

xTalálatx

Vagy ez is lehet megoldás:

[43] PS C:\> "Keresem: xTalálatx, az x-ek közti szöveget" -match "x[^x]+x"

True

[44] PS C:\> $matches[0]

xTalálatx

Itt azzal tettem a regexet szerényebbé, hogy az x utáni „nem x” karaktereket keresem x-ig. Ez azért is jó, mert jóval hatékonyabb ennek az ellenőrzése, nincs felesleges ide-oda járkálás a mintában. Nagytömegű feldolgozásnál sokkal gyorsabban eredményre jutunk.

Escape a Regex-ben

Ezen utóbbi példáknál gondot jelenthet, ha az „x”-nél valami ésszerűbb szeparátor szerepel a keresett szövegrészünk határaként. Ilyen elválasztó karakter szokott lenni mindenfajta zárójel, perjel, stb. Ezek a regexben is legtöbbször funkcióval bírnak, így ha rájuk keresünk, akkor „escape” karakterrel kell megelőzni ezeket. A regexben az „escape” karakter a visszafele-perjel (\), nem pedig a PowerShellben megszokott visszafele aposztróf (`)!

A fenti „x”-es példát cseréljük () zárójel-párra:

[45] PS C:\> "A zárójelek közti (szöveget) keresem" -match "\([^)]*\)"

True

[46] PS C:\> $matches[0]

(szöveget)

Megjegyzés:

Ne csak a $matches változó tartalmára hagyatkozzunk egy –match vizsgálat során, mert ha nincs találatunk, attól még a $matches tartalmazza egy korábbi –match eredményét:

[62] PS C:\> "baba" -match "b"

True

[63] PS C:\> $matches[0]

b

[64] PS C:\> "baba" -match "c"

False

[65] PS C:\> $matches[0]

b

Tudjuk, hogy mi, de hol van?

Most már tudjuk, hogy van-e a mintánknak megfelelő szövegrészünk, azt is tudjuk, hogy mi az, csak azt nem tudjuk, hogy hol találta a –match. Ez főleg akkor érdekes, ha több találatunk is lehet.

Az alapműködést már megbeszéltük, a regex balról jobbra elemez:

[3] PS C:\> "Minta az elején, végén is minta" -match "minta"

True

[4] PS C:\> $matches[0]

Minta

Erről meg is győződhetünk, hiszen a fenti találat nagy „M”-mel kezdődik, az pedig a szövegünk legeleje.

A regex lehetőséget biztosít, hogy a szövegünk végén keressük a mintát:

[7] PS C:\> "Minta az elején, végén is minta" -match "minta$"

True

[8] PS C:\> $matches[0]

minta

Itt a találat kis „m” betűs lett, így ez tényleg a szövegem vége. Tehát a szöveg végét egy „$” jel szimbolizálja a mintában. Ez azonban nem jelent fordított működést, azaz itt is balról jobbra történik a kiértékelés. A „$” jelet úgy lehet felfogni, mintha az az eredeti szövegemben is ott lenne rejtett módon, és így a minta tényleg csak a sor végén illeszkedik a szövegre. Például a szöveg utolsó szava:

[10] PS C:\> "Utolsó szót keresem" -match "\b\w+$"

True

[11] PS C:\> $matches[0]

keresem

A szöveg elejére is lehet hivatkozni a „kalap” (^) jellel. Tehát az a „kalap” jel más funkciójú a szögletes [] zárójelek között (negálás), mint a „sima” regex mintában, ahol szöveg elejét jelenti:

[15] PS C:\> "1234 Elején van-e szám?" -match "^\d+"

True

[16] PS C:\> $matches[0]

1234

[17] PS C:\> "Elején van-e 4321 szám?" -match "^\d+"

False

Az előző példában csak a szöveg elején található számot tekintjük a mintával egyezőnek.

Most már tudjuk, hogy az elején vagy a végén találtuk-e a mintának megfelelő szöveget, de vajon egy köztes szöveget, például szóközök közti szöveget hogyan tudunk úgy megtalálni, hogy magát a szóközöket ne tartalmazza a találat? Erre - egyik megoldásként - alkalmazhatjuk a csoportosítást a mintában:

[25] PS C:\> "Ez szóközök közötti" -match "\s(.+?)\s"

True

[26] PS C:\> $matches

 

Name                           Value

----                           -----

1                              szóközök

0                               szóközök

Itt a mintában található „(\.+?)” rész a gömbölyű zárójelek között egy „lusta” többszörözött (a ? lustítja el a + hatását) szókarakter al-minta a teljes minta részeként, más szóval csoport. A $matches[0] továbbra is a teljes találatot tartalmazza, de most már van egy $matches[1] is, ami az első találati csoport eredményét tartalmazza. Természetesen több csoportot is elkülöníthetünk, például számjegyek csoportjait:

[28] PS C:\> "T.: (+36-1) 123-4567" -match "\(\+(\d+)-(\d+)\)\s(\d{3}-\d{4})"

True

[29] PS C:\> $matches

 

Name                           Value

----                           -----

3                              123-4567

2                              1

1                              36

0                              (+36-1) 123-4567

A fenti példában egy telefonszámból választom ki külön az országkódot, a körzetszámot és a helyi számot. Természetesen egy ilyen mintát összerakni nem egyszerű segédeszköz nélkül. Erre használhatunk olyan eszközöket, mint például a 2.3.3 RegexBuddy fejezetben említésre kerülő eszköz:

 

40 . ábra Barátunk megint segít: értelmez és tesztel

Ez a segédprogram a mintánk különböző részeit színkódokkal jelzi, és a „Create” fülön részletes magyarázatot is kapunk az általunk beírt mintáról, észre tudjuk venni, hogy esetleg kifelejtettünk egy „escape” karaktert. A „Test” fülön meg ellenőrizni tudjuk, hogy megkapjuk-e találatként a helyes szöveget, illetve azt is, hogy a nem jó szöveg tényleg nem találat-e.

Ezeket a csoportokat nevesíthetjük is a jobb azonosíthatóság érdekében:

[47] PS C:\> "Telefon: (+36-1) 123-4567" -match "\(\+(?<ország>\d+)-(?<körze

t>\d+)\)\s(?<helyi>\d{3}-\d{4})"

True

[31] PS C:\> $matches

 

Name                           Value

----                           -----

körzet                         1

helyi                          123-4567

ország                         36

0                              (+36-1) 123-4567

Tehát a csoportot jelző nyitó zárójel mögé tett „?” és „<>” kacsacsőrök közötti címkével tudjuk nevesíteni a csoportot. Itt nyer értelmet, hogy a $matches miért hashtábla, és miért nem egyszerű tömb.

Mi van akkor, ha egy csoportra nincs szükségünk? Erre használhatjuk a „(?:)”, un. „nem rögzülő” csoportot:

[36] PS C:\> "123-4567" -match "(?<eleje>\d+)(?:-)(?<vége>\d+)"

True

[37] PS C:\> $matches

 

Name                           Value

----                           -----

eleje                          123

vége                           4567

0                              123-4567

A fenti példában a kötőjelet nem rögzülő csoportba helyeztem, így az nem is szerepel külön találatként, de természetesen a teljes találatban ($matches[0]) benne van.

Természetesen a csoportokra is alkalmazhatjuk a „?” többszörözés-jelölőt, ami jelenti ugye a 0 vagy 1 darabot, azaz kezelhetjük azokat a telefonszámokat is, ahol nincs országkód és körzetszám:

[42] PS C:\> "Telefon: (+36-1) 123-4567" -match "(\(\+(?<ország>\d+)-(?<körzet>\d+)\)\s)?(?<helyi>\d{3}-\d{4})"

True

[43] PS C:\> $matches

 

Name                           Value

----                           -----

körzet                         1

helyi                          123-4567

ország                         36

1                              (+36-1)

0                              (+36-1) 123-4567

Ilyenkor – a [42]-es példában látható módon – az egymásba ágyazott csoportok külön találatot adnak a listában, jelen esetben 1-es számmal. Nézzük, mi van akkor, ha nincs országkód és körzetszám:

[44] PS C:\> "Telefon: 123-4567" -match "(\(\+(?<ország>\d+)-(?<körzet>\d+)\)\s)?(?<helyi>\d{3}-\d{4})"

True

[45] PS C:\> $matches

 

Name                           Value

----                           -----

helyi                          123-4567

0                              123-4567

Most térjünk át a –match egy nagy korlátjához: csak az első találatig keres. Bár az előző példában használtuk a „?”-et, más többszörözések nem úgy működnek, ahogy szeretnénk. Vegyünk egy olyan mintát, amellyel el szeretnénk különíteni a szavakat, kezdjünk egyszerűség kedvéért három szóval:

[76] PS C:\> "egy kettő három" -match "(\w+)\s(\w+)\s(\w+)"

True

[77] PS C:\> $matches

 

Name                           Value

----                           -----

3                              három

2                              kettő

1                              egy

0                              egy kettő három

Meg is kaptuk a várt csoportokat. Vajon egyszerűsíthetjük-e ezt a kifejezést, egyszersmind felkészíthetjük-e a mintánkat változó számú szóra?

[84] PS C:\> "egy kettő három " -match "((?<szó>\w+)\W){3}"

True

[85] PS C:\> $matches

 

Name                           Value

----                           -----

szó                            három

1                              három

0                              egy kettő három

Már a fix számú vizsgálatot se tudtam elérni, a $matches nem jegyzi meg egy adott pozíciójú csoport korábbi értékeit. Itt csak a legutolsó szó, a „három” került bele a hashtáblába. Azaz hiába igaz az, hogy csak három szó egyidejű jelenléte teljesíti a feltételt, a végeredménybe csak az utolsó szó kerül bele. Ezt a problémát kicsit később oldom meg.

Tekintsünk előre és hátra a mintában

Egyelőre kanyarodjunk oda vissza, hogy a minta helyét valamilyen elválasztó karakter szabja meg. Ha magát a mintát nem akarjuk a találatban viszontlátni, akkor egyik megoldásként alkalmazhatjuk a csoportokat. Egy másik lehetőség a mintában az előretekintés és visszatekintés lehetősége. Például nézzük csak azokat a számokat, amelyek zárójelek között vannak:

[86] PS C:\> "sima: 12, nem jó: 11), ez sem: (22, ez jó: (46)" -match

>> "(?<=\()\d+(?=\))"

>> 

True

[87] PS I:\>$matches

 

Name                           Value

----                           -----

0                              46

Na, ez megint kezd kicsit hasonlítani az egyiptomi hieroglifákhoz. Nézzük meg, hogy hogyan vizualizálja ezt a RegexBuddy:

41 . ábra RegexBuddy értelmezi a mintát

A „(?<=xxx)” szerkezet „visszatekint”, azaz megvizsgálja, hogy az aktuális vizsgálati pozíció előtt az „xxx” minta megtalálható-e. Ha nem, akkor már meg is bukott a vizsgálat. A minta végén található „(?=xxx)” előretekint, azaz megvizsgálja, hogy az aktuális vizsgálati pozíció után az „xxx” minta megtalálható-e. Bár mindkét kitekintő minta zárójeles, de ahogy [87]-ben láthatjuk, nem adnak vissza találatot.

Ezen kitekintő mintáknak is van negatív változatuk, azaz pont azt keressük, hogy a mintánk előtt vagy után ne legyen valamilyen minta:

[22] PS I:\>"Árak: Banán:207 Alma:88" -match "(?<!Banán:)\d+"

True

[23] PS I:\>$matches[0]

07

Hoppá! Ez nem is jó. Mit is szerettem volna? A „nem banán” gyümölcs árát szerettem volna kiszedni. Azonban a mintám teljesül már a banán áránál is a 2-es után, hiszen a „2” az nem „Banán:”. Azaz ebből az a tanulság, hogy igen gyakran, a negatív kitekintés mellé pozitívot is kell rakni, mert a nem egyezés az nagyon sokféleképpen teljesülhet. A mi esetünkben tehát a jó megoldás:

[24] PS I:\>"Árak: Banán:207 Alma:88" -match "(?<!Banán:)(?<=:)\d+"

True

[25] PS I:\>$matches[0]

88

Azaz itt most a „pozitív” rész a „:” kettőspont megléte. Ez a két feltétel együttesen már kielégíti a keresési feladatot.

Megjegyzés:

Felmerülhet a tisztelt olvasóban, hogy van-e értelme egykarakteres esetben a negatív körültekintésnek, hiszen használhatnánk negatív karakterosztályt is. Nézzük, hogy mi a különbség a kettő között. Keresem a nem számjeggyel folytatódó „a” betűt:

[26] PS C:\> "CAS" -match "a[^\d]"

True

[27] PS C:\> $matches[0]

AS

[28] PS C:\> "CAS" -match "a(?!\d)"

True

[29] PS C:\> $matches[0]

A

[30] PS C:\> "CA" -match "a[^\d]"

False

[31] PS C:\> "CA" -match "a(?!\d)"

True

[32] PS C:\> $matches[0]

A

A [27]-es és [29]-es sorokban látható a különbség az eredményben, de ennél súlyosabb a helyzet akkor, ha nincs az „a” betű után „nem számjegy”, hiszen ekkor a negatív karakterosztály fals eredményt ad, mert ő a minta azon helyére mindenképpen akar valamit passzítani, míg a negatív előretekintés esetében a mintának nincs hiányérzete, ha nincs ott semmi már.

Nézzünk egy kicsit gyakorlatiasabb példát! Keresem egy ilyen (legacy exchange DN formátumú) szöveg utolsó ’cn=’ utáni részét:

/o=Exchange Org/ou=First Administrative Group/cn=Recipients/cn=Valaki Vendel

Megoldás:

PS C:\> $dn = "/o=Exchange Org/ou=First Administrative Group/cn=Recipients/cn=V

alaki Vendel"

PS C:\> $dn -match "(?<=cn=)(?!.*?cn=).*" | out-null; $matches[0]

Valaki Vendel

A regex kifejezés úgy épül itt fel, hogy keresem az a bármilyen szöveget (legvégén levő .*), ami előtt úgy van cn= (?<=cn=), hogy mögötte már nincs bármennyi karakter után sem cn=, azaz (?!.*?cn=). Itt a negatív előretekintésnél lusta a bármennyi karakter, hogy hatékonyabb legyen a futás.

A mintám visszaköszön

Térjünk vissza a csoportokhoz! Hogyan lehetne felismerni azokat a szavakat, amelyek ugyanolyan elválasztó karakterek között vannak:

[16] PS C:\> "-valami-" -match "(\W)\w+\1"

True

[17] PS C:\> "/valami/" -match "(\W)\w+\1"

True

Azaz egy csoport eredményét már magában a mintában is felhasználhatom. A csoportra történő hivatkozás formátuma „\1”, „\2”, stb.

Fontos!

Ha egy csoportot ilyen visszahivatkozásban akarjuk szerepeltetni, akkor az a csoport nem lehet „nem rögzülő” csoport, azaz a „?:” jelölést nem használhatjuk.

Természetesen nevesített csoportokra is lehet hivatkozni, ennek formája: „\k<név>”:

[28] PS C:\> "A kétszer kétszer rossz" -match "(?<szó>\b\w+\b).+\k<szó>"

True

[29] PS C:\> $matches.szó

kétszer

Változatok a keresésre

Az eddigi példákban azt láthattuk, hogy a –match operátor, hasonlóan a többi szöveges operátorhoz – nem kis-nagybetű érzékeny. De – mint ahogy a többinél is – ennek is van kis-nagybetű érzékeny változata is, a -cmatch :

[1] PS I:\>"Ebben Kis Nagy van" -cmatch "kis"

False

Ha pont a nem egyezőséget akarjuk vizsgálni, arra a –notmatch  vagy a kis-nagybetű érzéketlen –cnotmatch  használható:

[2] PS I:\>"Nem szeretem a kacsamajmot!" -notmatch "kacsamajom"

True

[3] PS I:\>$matches

[4] PS I:\>

Természetesen a $true válasz ellenére itt a $matches nem ad találatot.

A –match még ezt is lehetővé teszi, hogy egy keresési mintán belül váltogassuk a különböző opciókat. Erre a speciális üzemmód jelölő ad lehetőséget:

(?imnsx-imnsx)

(?imnsx-imnsx: )

Jelentése

(a mintára),

(vagy az adott csoportra vonatkozóan)

i

kis-nagybetű érzéketlen

m

többsoros üzemmód, a ^ és $ metakarakterek a sorvégekre is illeszkednek

n

explicit capture”, azaz csak a nevesített csoportok rögzülnek, a névnélküli csoportok nem

s

egysoros üzemmód, a „.” metakarakter illeszkedik a sorvégkarakterre

x

szóközt figyelmen kívül hagyja a mintában, ilyenkor csak a \s-sel hivatkozhatunk a szóközre

A fenti táblázat fejlécében található jelzés egy kis magyarázatra szorul. A (?imnsx-imnsx) jelzés, a végén kettőspont nélkül jelzi, hogy ez az egész mintára vonatkozik. Természetesen nem használjuk egyszerre az össze opciót bekapcsolt és kikapcsolt módon. Például:

"szöveg" -match "(?im-sx)minta"

Ebben a példában az „i” és „m” opció be van kapcsolva, az „s” és az „x” meg ki van kapcsolva.

Ha egy mintán belül többfajta üzemmódot szeretnénk, akkor jön a kettőpontos változata ezen kapcsolók használatának:

"szöveg" -match "minta(?-i:M)"

A fenti példában csak az „M” betűre, mint részmintára vonatkozik a kis-nagybetű érzéketlenség kikapcsolása.

Nézzünk ezek használatára gyakorlati példákat!

Tegyük kis-nagybetű érzékennyé a vizsgálatot:

[1] PS C:\> "a NAGY betűst keresem" -match "(?-i)NAGY"

True

[2] PS C:\> "a NAGY betűst keresem" -match "(?-i)nagy"

False

Kikapcsoltam az érzéketlenséget, azaz kis-nagybetű érzékennyé tettem a vizsgálatot, és csak a nagybetűs minta esetében kaptam találatot.

Nézzük a többsoros szöveg vizsgálatának opcióit:

[5] PS C:\> $szöveg="első sor1

>> második sor2"

>> 

[6] PS C:\> $szöveg -match "sor\d$"

True

[7] PS C:\> $matches[0]

sor2

[8] PS C:\> $szöveg -match "(?m)sor\d$"

True

[9] PS C:\> $matches[0]

sor1

Létrehoztam egy kétsoros szöveget, keresem a sorvégi „sor” szót és egy számjegyet. A két sorvégnél a számmal teszek különbséget, hogy a találatból kiderüljön, hogy melyiket is találtuk meg. A [6]-os sorban nem szóltam a -match-nek, hogy lelkileg készüljön fel a többsoros szövegek vizsgálatára, így a sztringvég karakter csak a szöveg végére illeszkedik, így a találatunk a sor2 lett. Ha szólok neki, hogy a szövegem több soros, akkor az első sor vége is találatot ad. A „szólás” a (?m) többsoros üzemmód-kapcsolóval történt.

Nézzünk egy példát a nem nevesített csoportok eldobására:

[10] PS I:\>"Eleje vége" -match "(?n)(\w+)\s(?<ketto>\w+)"

True

[11] PS I:\>$matches

 

Name                           Value

----                           -----

ketto                          vége

0                              Eleje vége

 

 

[12] PS I:\>"Eleje vége" -match "(?-n)(\w+)\s(?<ketto>\w+)"

True

[13] PS I:\>$matches

 

Name                           Value

----                           -----

ketto                          vége

1                              Eleje

0                              Eleje vége

A fenti példában látható, hogy amikor a [10]-es sorban bekapcsoltam az „n” kapcsolót, akkor külön csoportként nem szerepel az első, név nélküli csoport (de természetesen a teljes találat részét képezi), míg a kikapcsolt, alaphelyzet szerinti működés mellett (12]-es sor) az első, nevesítetlen csoport is létrejött.

Nézzük az egysoros kapcsolót. Kicsit zavaró a „többsoros”, „egysoros” elnevezés, hiszen ezek egymás mellett is megférnek, mert más metakarakter működését szabályozzák. Szóval nézzünk egy példát az egysoros üzemmódra:

[14] PS C:\> $szöveg="Eleje közepe

>> újsor vége"

>>

[15] PS C:\> $szöveg -match "eleje.+vége"

False

[16] PS C:\> $szöveg -match "(?s)eleje.+vége"

True

A [14]-es sorban vizsgálom, hogy illeszkedik-e a kétsoros szövegem olyan mintára, amely egy „eleje” karaktersorozattal kezdődik, és egy „vége” karaktersorozattal végződik. Alapesetben nem kaptam találatot, hiszen a „.+” felakad a sor végén. Míg ha „kierőltetem” az egysoros értelmezést, ahogy az a [16]-os sorban tettem, akkor a „.+” keresztülgázol a sorvégkarakteren is és megkapom a találatomat.

Szóköz mintaként való alkalmazása:

[17] PS C:\> "Valami más" -match "(?x)i m"; $matches[0]

False

 

[18] PS C:\> "Valami más" -match "(?-x)i m"; $matches[0]

True

i m

Ebben a példában azt láthatjuk, hogy ha lusták vagyunk a szóköz \s-ként való mintában való hivatkozásához, akkor használhatjuk az igazi szóköz karaktert is, ha ezt az üzemmódot bekapcsoljuk. Lehetőség szerint ennek használatát azért kerüljük, mert sok regex változat nem ismeri ezt a lehetőséget, meg a minták olvashatósága sem biztos, hogy a legjobb lesz.

Tudjuk, hogy mi, de hányszor?

Az eddigi példákban azt tapasztaltuk, hogy a mintánkat az első találatig keresi a ‑match és változatai. Még a csoportok használatával is csak akkor tudjuk megkeresni a többszörös találatot, ha mi azt a mintát erre eleve felkészítjük, méghozzá meghatározott darabszámmal.

A PowerShell saját –match operátorával nem is tudunk többszörös találatot előcsiholni, de szerencsére van erre a cmdlet (lásd következő fejezetet), és – mint ahogy már többször láttuk – a .NET Framework ebben is segítségünkre van:

[41] PS C:\> $minta = [regex] "\w+"

Itt most a [regex]  típusjelölővel hozok létre mintát. Nézzük meg ennek tagjellemzőit:

[42] PS C:\> $minta | gm

 

 

   TypeName: System.Text.RegularExpressions.Regex

 

Name                MemberType Definition

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

Equals              Method     System.Boolean Equals(Object obj)

GetGroupNames       Method     System.String[] GetGroupNames()

GetGroupNumbers     Method     System.Int32[] GetGroupNumbers()

GetHashCode         Method     System.Int32 GetHashCode()

GetType             Method     System.Type GetType()

get_Options         Method     System.Text.RegularExpressions.RegexOptio...

get_RightToLeft     Method     System.Boolean get_RightToLeft()

GroupNameFromNumber Method     System.String GroupNameFromNumber(Int32 i)

GroupNumberFromName Method     System.Int32 GroupNumberFromName(String n...

IsMatch             Method     System.Boolean IsMatch(String input), Sys...

Match               Method     System.Text.RegularExpressions.Match Matc...

Matches             Method     System.Text.RegularExpressions.MatchColle...

Replace             Method     System.String Replace(String input, Strin...

Split               Method     System.String[] Split(String input), Syst...

ToString            Method     System.String ToString()

Options             Property   System.Text.RegularExpressions.RegexOptio...

RightToLeft         Property   System.Boolean RightToLeft {get;}

A metódusokat nézve mindent tud, amit eddig láttunk, de van egy sokat ígérő Matches() metódusunk is, ami többes számot sejtet (reméljük nem egyes szám harmadik személyt! J):

[43] PS C:\> $eredmény = $minta.matches("Ez a szöveg több szóból áll")

[44] PS C:\> $eredmény

 

 

Groups   : {Ez}

Success  : True

Captures : {Ez}

Index    : 0

Length   : 2

Value    : Ez

 

Groups   : {a}

Success  : True

Captures : {a}

Index    : 3

Length   : 1

Value    : a

 

Groups   : {szöveg}

Success  : True

Captures : {szöveg}

Index    : 5

Length   : 6

Value    : szöveg

 

Groups   : {több}

Success  : True

Captures : {több}

Index    : 12

Length   : 4

Value    : több

 

Groups   : {szóból}

Success  : True

Captures : {szóból}

Index    : 17

Length   : 6

Value    : szóból

 

Groups   : {áll}

Success  : True

Captures : {áll}

Index    : 24

Length   : 3

Value    : áll

Egészen érdekes és sokat sejtető eredményt kaptunk. Nézzük meg a get-member cmdlettel, hogy ez mi:

[45] PS C:\> Get-Member -InputObject $eredmény

 

 

   TypeName: System.Text.RegularExpressions.MatchCollection

 

Name               MemberType            Definition

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

CopyTo             Method                System.Void CopyTo(Array array,...

Equals             Method                System.Boolean Equals(Object obj)

GetEnumerator      Method                System.Collections.IEnumerator ...

GetHashCode        Method                System.Int32 GetHashCode()

GetType            Method                System.Type GetType()

get_Count          Method                System.Int32 get_Count()

get_IsReadOnly     Method                System.Boolean get_IsReadOnly()

get_IsSynchronized Method                System.Boolean get_IsSynchroniz...

get_Item           Method                System.Text.RegularExpressions....

get_SyncRoot       Method                System.Object get_SyncRoot()

ToString           Method                System.String ToString()

Item               ParameterizedProperty System.Text.RegularExpressions....

Count              Property              System.Int32 Count {get;}

IsReadOnly         Property              System.Boolean IsReadOnly {get;}

IsSynchronized     Property              System.Boolean IsSynchronized {...

SyncRoot           Property              System.Object SyncRoot {get;}

Ez pont az, amit szerettünk volna: egy tömb, ami tartalmazza az összes találatot! Hogyan lehet ezt az eredményt kezelni? Például egy ciklussal kiírathatom az egyes találati elemeket:

[46] PS C:\> foreach($elem in $eredmény){$elem.value}

Ez

a

szöveg

több

szóból

áll

Megjegyzés:

Vigyázzunk, hogy a [regex] típusnak van egy match() metódusa is, de az szintén csak egy találatod ad vissza, hasonlóan a –match operátorhoz.

Ahogy a [43]-as sor kimenetében láttuk, a [regex] objektumok rendelkeznek replace() metódussal is, de van split() is, amellyel a minta találati helyeinél lehet feltördelni a szöveget, amelyre alkalmazzuk. Nézzünk erre egy gondolatébresztő példát:

[49] PS C:\> $minta = [regex] '\W+'

[50] PS C:\> $minta.split("Ebben {van}, minden-féle (elválasztó) [jel]")

Ebben

van

minden

féle

elválasztó

jel

Ebben a példában feldaraboltam a szövegemet mindenféle szóelválasztó karakternél. Ugye ugyanezt az eredményt nem tudtam volna elérni a sztringek split() metódusával:

[51] PS C:\> ("Ebben {van}, minden-féle (elválasztó) [jel]").split()

Ebben

{van},

minden-féle

(elválasztó)

[jel]

Regex opciók

Az előbb látott [regex] típusnak még egyéb képességei is vannak a –match operátorhoz képest. A legfontosabb az opciók használatának lehetősége, amelyeket az alábbi RegexOptions felsorolásban láthatunk:

[29] PS C:\> [System.Text.RegularExpressions.RegexOptions] | gm -MemberType pro

perty -Static

 

 

   TypeName: System.Text.RegularExpressions.RegexOptions

 

Name                    MemberType Definition

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

Compiled                Property   static System.Text.RegularExpressions.Re...

CultureInvariant        Property   static System.Text.RegularExpressions.Re...

ECMAScript              Property   static System.Text.RegularExpressions.Re...

ExplicitCapture         Property   static System.Text.RegularExpressions.Re...

IgnoreCase              Property   static System.Text.RegularExpressions.Re...

IgnorePatternWhitespace Property   static System.Text.RegularExpressions.Re...

Multiline               Property   static System.Text.RegularExpressions.Re...

None                    Property   static System.Text.RegularExpressions.Re...

RightToLeft             Property   static System.Text.RegularExpressions.Re...

Singleline              Property   static System.Text.RegularExpressions.Re...

Az ismerős ExplicitCapture, IgnoreCase, stb. mellett a RightToLeft lehet számunkra az igazi hasznos újdonság. Ennek alkalmazásához már a típus objektumának előállításánál van lehetőségünk az alábbiak szerint:

[31] PS C:\> $r = New-Object -TypeName System.Text.RegularExpressions.Regex -Ar

gumentList "\w", "RightToLeft"

És innen már a szokásos módon használhatjuk a regex objektumot, az eredményben a találatok jobbról balra jönnek elő:

[32] PS C:\> $r.Match("abc").value

c

A ^ és a $ jelek jelentése nem változik, azaz:

[33] PS C:\> $r = New-Object -TypeName System.Text.RegularExpressions.Regex -Ar

gumentList "^\w", "RightToLeft"

[34] PS C:\> $r.Match("abc").value

a

A felnyíl továbbra is a minta bal oldali elejét, a dollár jobb oldali végét jelenti.

AND a regexben

Előfordulhat olyan eset is, amikor a mintámnak egyszerre két vagy több feltételnek is eleget kell tennie. Ilyen lehet például az, hogy a mintámban legyen számjegyet ÉS hogy az a számjegy nem kettő. Persze ez úgy is megfogalmazható lenne, hogy felsorolom az összes kettőn kívüli számjegyet, de ez egyrészt bonyolultabb, másrészt bizonyos esetekben nem is olyan egyszerű a felsorolás.

Hogyan lehet tehát ÉS feltételt készíteni? Az eddigi építőkövekből ezt meg lehet oldani egy sima minta és egy másik minta visszatekintésével:

minta(?<=másikminta)

Vagy ha az ÉS feltétel második tagja tagadó, akkor így:

minta(?<!másikminta)

Vegyük akkor a felvezetőben említett példát, keresem a számjegyet, ami nem 2:

[6] PS C:\> $r = [regex] "\d(?<!2)"

[7] PS C:\> $r.Matches("abc1234") | Select-Object -ExpandProperty value

1

3

4

Természetesen nem csak kéttagú lehet egy ilyen ÉS feltétel, hanem akármennyi, a visszatekintések számának növelésével.

IF-THEN-ELSE vizsgálat regexben

Nézzünk végül egy olyan szerkezetet, amellyel IF-THEN-ELSE jellegű vizsgálatokat tudunk végezni. Legyen a példánk az, hogy keresem a szavakat és a telefonszámokat. A telefonszám onnan ismerszik meg az egyszerűség kedvéért, hogy számjegyekből, kötőjelekből és szóközökből állhat, valamint előtte ott egy címke, hogy ’Telefon:’. Előfordulhat a szövegben más címke is, azok nem kellenek nekünk.

Azaz itt egy olyan regex kifejezés kell, ami így szól: ha a telefonszám előtt az áll, hogy ’Telefon:’, akkor jó az nekünk, egyébként a kettőspont nélküli szavakat keressük.

Szerencsére a regex tud ilyen IF-THEN-ELSE szerkezeteket is ilyen formában: (?(?=feltétel)akkor|egyébként). Azaz, ha teljesül a feltétel rész, akkor keresse az akkor részt, egyébként keresse az egyébként részt.

Nézzük mindezt a konkrét esetünkre:

[17] PS C:\> "cím: szöveg." -match "(?(?<=Telefon:)[\d-\s]+|\b\w+\b(?!:\s*))"

True

[18] PS C:\> $Matches[0]

szöveg

[19] PS C:\> "Telefon:12-34" -match "(?(?<=Telefon:)[\d-\s]+|\b\w+\b(?!:\s*))"

True

[20] PS C:\> $Matches[0]

12-34

Látható a fenti példákból, hogy tényleg működik ez a szerkezet.

Megjegyzés

Nagyon vigyázni kell ennél a szerkezetnél, hogy az ELSE ág nehogy „benyelje” a THEN ág mintáját. Például, ha túl megengedő az ELSE ág, és az IF rögtön nem válik igazzá, nem azt kapjuk, amire számítunk:

[22] PS C:\> "Telefon: 123-4567" -match "(?(?<=Telefon:)[\d-\s]+|.+)"

True

[23] PS C:\> $Matches[0]

Telefon: 123-4567

Itt tehát a „T” betűnél nem válik igazzá, hogy „Telefon:” van előtte, azaz felélesedik az ELSE ág, ami a „.+”-nak köszönhetően benyelte az egész mintát, még a címkét is, így az IF-nek innentől kezdve már nem jut semmi.

Úgyhogy ezt mindig alaposan ellenőrizzük le.

Regex cmdlettel (Select-String)

A PowerShell 2.0-ban a több találatot is megjelenítő regex kifejezéseket már a Select-String  cmdlet is tudja! Nézzük, hogy hogyan:

[1] PS C:\> "ablak", "ajtó", "ház" | Select-String -Pattern "a"

 

ablak

ajtó

Első ránézésre úgy néz ki, mintha egyszerűen csak kiválogatta volna azokat a sztringeket, amelyekben szerepel az „a” minta. De nézzük kicsit részletesebben az eredményt:

[2] PS C:\> "ablak", "ajtó", "ház" | Select-String -Pattern "a" | Format-List

 

 

IgnoreCase : True

LineNumber : 1

Line       : ablak

Filename   : InputStream

Path       : InputStream

Pattern    : a

Context    :

Matches    : {a}

 

IgnoreCase : True

LineNumber : 2

Line       : ajtó

Filename   : InputStream

Path       : InputStream

Pattern    : a

Context    :

Matches    : {a}

Látható, hogy a Matches tulajdonságban megtalálható egyelőre az első találat. Ha az összesre kíváncsiak vagyunk, akkor használjuk az –AllMatches kapcsolót:

[8] PS C:\> "ablak", "ajtó", "ház" | Select-String -Pattern "a" -AllMatches | F

ormat-List

 

 

IgnoreCase : True

LineNumber : 1

Line       : ablak

Filename   : InputStream

Path       : InputStream

Pattern    : a

Context    :

Matches    : {a, a}

 

IgnoreCase : True

LineNumber : 2

Line       : ajtó

Filename   : InputStream

Path       : InputStream

Pattern    : a

Context    :

Matches    : {a}

Látható, hogy így már az első illeszkedést kielégítő sztringnél a Matches tulajdonságban minden egyező találat megtalálható. A Select-String közvetlenül tud szövegfájlokban is keresni, ezt a képességét majd a gyakorlati rész 2.6.7 Szövegfájlok beolvasása (Get-Content) fejezetben lesz szó.

Csere (-replace)

Sok esetben azért keresünk egy mintát, hogy az illeszkedő részeket kicseréljük valami másra. Ez a valami más akár lehet egy találati csoport is. Például cseréljük ellenkező sorrendűre egy IP címben szereplő számokat:

[64] PS C:\> "Ezt kellene megfordítani: 192.168.1.2 reverse zónához" -replac

e "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})", '$4.$3.$2.$1'

Ezt kellene megfordítani: 2.1.168.192 reverse zónához

Fontos!

A –replace operátornál a minta és a csereszabály között vessző van és a csereszabály nem macskakörmök, hanem aposztrófok között kell, hogy álljon, egyébként a csoporthivatkozásokat ($1, $2,...) a PowerShell változóként értékelné, holott ezek nem változók, hanem a [regex] formai elemei!

Látjuk, hogy itt a mintacsoportok résztalálataira $1, $2,... formában tudunk hivatkozni, az indexek nem nullától indulnak, hanem 1-től. A –replace-en kívül nem lehet hozzáférni a $1 és a többi változóhoz, és a –replace által végrehajtott illeszkedésvizsgálat eredménye sem olvasható ki, nem keletkezik $matches változó sem.

Nevesített csoportokat is használhatunk a cserében:

PS C:\> "http://powershellkonyv.hu" -replace "http(s)?://(?<host>[^\.]+)\..*",'

${host}'

Azaz a ${csoportnév} szerkezettel lehet a nevesített csoportokra hivatkozni, itt is vigyázzunk az idézőjelezéssel!

A –replace szerencsére már alaphelyzetben a mintának megfelelő összes szövegrészt kicseréli:

[65] PS C:\> "Szóköz kicsrélése aláhúzásra" -replace "\s", '_'

Szóköz_kicsrélése_aláhúzásra

Azonban ez nem rekurzív, azaz ha a csere után is marad cserélni való, akkor arra nekünk kell újra meghívni a –replace-t:

[66] PS C:\> "Felesleges     szóközök    eltávolítása" -replace "\s\s", ' '

Felesleges   szóközök  eltávolítása

A fenti példában látszik, hogy a sok egymás melletti szóközt szeretném egyre cserélni azáltal, hogy kettőből csinálok egyet. A fenti módszerrel ez nem működik, hiszen csak egyszer szalad végig, a megoldásban még bőven marad több egymás melletti szóköz. Erre egy jobb minta alkalmazása a megoldás:

[67] PS C:\> "Felesleges    szóközök    eltávolítása" -replace "\s{2,}", ' '

Felesleges szóközök eltávolítása

Mint ahogy a –match-nek is, a –replace-nek is van kis-nagybetű érzékeny változata, a –creplace :

[68] PS C:\> "Csak a nagy 'A'-t cserélem" -creplace "A", 'a'

Csak a nagy 'a'-t cserélem

A –replace még tud néhány érdekességet:

[56] PS C:\> "egy kettő három" -replace "kettő", '$`'

egy egy  három

A $` szimbólum jelképezi a találatig terjedő szövegrészt.

[58] PS C:\> "egy kettő három" -replace "kettő", "$'"

egy  három három

A $' szimbólum jelképezi a találat utáni szövegrészt. Vigyázni kell, hogy itt a PowerShell idézőjel-kezelési szabályai miatt vagy macskakörmöt (”) használunk a jel körül, vagy duplázzuk az aposztrófot: '$'''.

[61] PS C:\> "egy kettő három" -replace "kettő", '$_'

egy egy kettő három három

 A $_ szimbólum a teljes mintát szimbolizálja. Itt megint ügyelni kell arra, hogy a PowerShellben a $_ változót szimbolizál, így a regex számára szó szerint ezt úgy tudjuk átadni, ha aposztrófot használunk.

[63] PS C:\> "egy kettő három" -replace "kettő", '$+$+'

egy kettőkettő három

A $+ szimbólummal a találatot szimbolizálhatjuk, így a fenti példában duplikálni tudtam a „kettő”-t.

Még egy dolog megjegyzendő a csere operátorával kapcsolatban, az, hogy ez már alaphelyzetben az összes keresett kifejezést megtalálja és cseréli:

PS C:\> "baba" -replace "a", "i"

bibi

Sőt! Nem csak hogy egy szövegen belül mindent megtalál, hanem rögtön gyűjtemény is átadható neki:

PS C:\> "baba", "lala" -replace "a", "i"

bibi

lili

Csere .NET osztállyal

Ha még bonyolultabb cserére van szükségünk, akkor ki kell nyúljunk a [regex] .NET osztályhoz. Ennek a replace statikus metódusa kínál számunkra további lehetőségeket:

PS C:\> [regex]::Replace

 

OverloadDefinitions

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

static string Replace(string input, string pattern, string replacement)

static string Replace(string input, string pattern, string replacement, System

.Text.RegularExpressions.RegexOptions options)

static string Replace(string input, string pattern, string replacement, System

.Text.RegularExpressions.RegexOptions options, timespan matchTimeout)

static string Replace(string input, string pattern, System.Text.RegularExpress

ions.MatchEvaluator evaluator)

static string Replace(string input, string pattern, System.Text.RegularExpress

ions.MatchEvaluator evaluator, System.Text.RegularExpressions.RegexOptions opt

ions)

static string Replace(string input, string pattern, System.Text.RegularExpress

ions.MatchEvaluator evaluator, System.Text.RegularExpressions.RegexOptions opt

ions, timespan matchTimeout)

A fenti listából látható, hogy a 3-6 szintaxisokban a harmadik paraméter lehet egy „System.Text.RegularExpressions.MatchEvaluator evaluator” érték, ami PowerShellre lefordítva egy szkriptblokk. Ez a szkriptblokk írja le azt a szabályt, hogy mire is szerenénk cserélni a talált mintáinkat. Ráadásul itt az $args[0].value segítségével hivatkozhatunk arra, amit éppen talált a keresés és ezzel egyéb műveleteket is végezhetünk.

Nézzük azt a példát, hogy a 0-tól különböző számokat szeretnénk eggyel növelni:

PS C:\> [regex]::Replace("PowerShell 2.0", "[1-9]", {[int]$args[0].value + 1})

PowerShell 3.0

Mivel a mintának megfelelő szövegrészek mindig string adattípusok, így ezt egésszé kell alakítani mielőtt növelni tudom eggyel.



Word To HTML Converter