Regulární výrazy a PowerShell
V minulém díle jsme si ukázali prohledávání textu pomocí operátorů a cmdletu Select-String. Dnes hledání rozšíříme o regulární výrazy.
Some people, when confronted with a problem, think
“I know, I'll use regular expressions.” Now they have two problems.
Tento známý citát (více viz Source of the famous “Now you have two problems” quote) mluví asi za vše. Především panuje jistá averze vůči regulárním výrazům „protože jsou složitý a divný“. Nechci se pouštět do ukázek typu „takhle složitě se to dá udělat“, příklady si můžete najít sami např. na regexlib.com.
Operátor –match
V minulém díle jsem schválně přeskočil jeden operátor: -match. Tento operátor vyhodnotí regulární výraz oproti danému textu a vráti hodnotu $true, pokud je nalezena shoda. Výsledek je uložen do proměnné $matches. Ukážeme si na něm některé základní konstrukce pro tvorbu regulárních výrazů. Vždy platí, že na levé straně operátoru –match uvádíme text, ve kterém hledáme a na pravé straně zapisujeme daný regulární výraz.
PS C:\> 'PowerShell' –match 'hell'
True
PS C:\> $matches
Name Value
---- -----
0 hell
V tomto nejjednodušším případě jsme hledali, jestli je ve vstupním textu „PowerShell“ obsažen text „hell“ (což je v tomto případě regulární výraz skládající se ze čtyř znaků řazených danou posloupností). Operátor vrátil hodnotu $true a proměnná $matches obsahuje část textu, který splňuje zadání.
PS C:\> 'PowerShell' -match 'he..'
True
PS C:\> $matches
Name Value
---- -----
0 hell
Tečka zastupuje jakýkoli znak. Je vidět, že ve výsledku jsou tečky nahrazeny znaky l.
PS C:\> 'PowerSuperShell' -match '(Power).*(Shell)'
True
PS C:\> $matches
Name Value
---- -----
2 Shell
1 Power
0 PowerSuperShell
Trošku přitvrdíme, ale zůstaneme na zemi. Pomocí závorek vytvoříme skupiny, každá ze skupin je poté v proměnné $matches uložena jako jeden záznam.
Dále jsme zavedli hvězdičku, která nám říká, že znak předcházející se může opakovat libovolněkrát (i 0krát!). Přeloženo do češtiny, předchozí regulární výraz znamená: Najdi znaky/slovo Power, poté přeskoč libovolný počet znaků a najdi znaky/slovo Shell. Ve výsledku vidíme, že na pozici 0 se uložil celý prohledávaný text, na pozici 1 první hledaný text a na pozici 2 druhý hledaný text. Mimo znaku hvězdičky jsou v regulárních výrazech ještě další dva podobné: otazník (?) a znaménko plus (+). Nejprve ukážeme příklad.
PS C:\> 'PowerShellll' -match 'el+'
True
PS C:\> $matches
Name Value
---- -----
0 ellll
PS C:\> 'PowerShellll' -match 'el?'
True
PS C:\> $matches
Name Value
---- -----
0 e
První příklad říká, že znak před + se může vyskytnout v textu jednou a vícekrát, proto vidíme ve výsledku znak l opravdu čtyřikrát. Naproti tomu druhý příklad – otazník – říká, že znak před ním se může objevit jednou a nebo vůbec! Proto se nám znak l ve výsledném textu neobjeví. Fakticky máme ve výsledku již první výskyt písmene e (ze slova Power), protože splňuje podmínku, že se za ním znak l nevyskytuje. Shrňme si předchozí odstavce do jednoduché tabulky.
Znak
Význam
*
Žádný nebo libovolný počet výskytů
?
Žádný nebo jeden výskyt
+
Jeden nebo více výskytů
Zkuste si za pomoci předchozí tabulky určit výsledky následujících ukázek.
PS C:\> 'aaa' -match 'a*'
PS C:\> 'aaa' -match 'a?'
PS C:\> 'aaa' -match 'a+'
PS C:\> 'aaa' -match 'b*'
PS C:\> 'aaa' -match 'b?'
PS C:\> 'aaa' -match 'b+'
Kolikrát myslíte, že výsledkem bude $true? Pokud jste „tipovali“ že pětkrát, máte pravdu. Pokud jste výsledek netrefili, zkuste se znovu podívat do předchozí tabulky a koukněte se následující výsledky:
PS C:\> 'aaa' -match 'a*'; $matches
True
Name Value
---- -----
0 aaa
PS C:\> 'aaa' -match 'a?'; $matches
True
Name Value
---- -----
0 a
PS C:\> 'aaa' -match 'a+'; $matches
True
Name Value
---- -----
0 aaa
PS C:\> 'aaa' -match 'b*'; $matches
True
Name Value
---- -----
0
PS C:\> 'aaa' -match 'b?'; $matches
True
Name Value
---- -----
0
PS C:\> 'aaa' -match 'b+'; $matches
False
Name Value
---- -----
0
Ještě jednou upozorňuji na „zradu“ v podobě formulace „žádný výskyt“. Proto i zcela „nesmyslný“ test ('a' -match 'b?') vrátí hodnotu $true. Nyní už určitě chápete citát ze začátku článkuVeselý obličej.
Občas se hodí vědět přesný počet výskytu určitých znaků. V tomto případě lze využít rozsah uvedený ve { složených závorkách }.
PS C:\> 'abbabbbbabbbbbb' -match 'ab{2}'; $Matches
True
Name Value
---- -----
0 abb
PS C:\> 'abbabbbbabbbbbb' -match 'ab{3,}'; $Matches
True
Name Value
---- -----
0 abbbb
PS C:\> 'abbabbbbabbbbbb' -match 'ab{5,8}'; $Matches
True
Name Value
---- -----
0 abbbbbb
{2} udává, že hledáme přesně dva výskyty znaku před závorkou, {3,} určuje, že chceme tři a více výskytů a konečně {5,8} říká, že hledáme četnost mezi pěti a osmi výskyty. Nyní už se můžeme pustit i do o něco složitějších konstrukcí. Potřebujete zkotrolovat telefonní číslo? Všechny následující příklady vrátí $true, pokud zadáte devítimístné číslo.
PS C:\> '123456789' -match '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
True
PS C:\> '123456789' -match '[0-9]{9}'
True
PS C:\> '123456789' -match '\d{9}'
True
Vzpomínáte na operátor rozsahu z minulého dílu? [0-9] říká, že hledáme čísla v daném rozsahu. V prvním příkladu tento rozsah zkopírujeme devětkrát, ve druhém použijeme daný počet výskytů {9}. Poslední příklad využívá definovanou množinu znaků (character class) - \d znamená jakoukoli číslici. Vzhledem k tomu, že se dnešní díl opět trochu natáhl, povíme si o množinách znaků více příště.
Při práci s operátorem –match se vám stane, že při použití ve funkci vám začne vadit vypisování $true nebo $false při každém porovnání. Pokud chcete pracovat pouze s výsledky, můžete výstup přesměrovat pomocí cmdletu Out-Null. Porovnejte následující výstupy.
PS C:\> $p = 'PowerShell'
PS C:\> foreach ($a in $p,$p,$p,$p,$p) { $a -match 'hell' }
True
True
True
True
True
PS C:\> foreach ($a in $p,$p,$p,$p,$p) { $a -match 'hell'; $matches[0] }
True
hell
True
hell
True
hell
True
hell
True
hell
PS C:\> foreach ($a in $p,$p,$p,$p,$p) { $a -match 'hell' | Out-Null; $matches[0] }
hell
hell
hell
hell
hell
V prvním příkladu vidíme pouze logický výsledek regulárního výrazu. Ve druhém přidáváme i výsledek, pokud chceme vidět pouze tento výsledek, použijeme zmiňovaný cmdlet Out-Null.