Abstrakt:
Workshop zaměřený pro začátečníky i pokročilejší uživatele týkající
se popisu řetězců pomocí regulárních výrazů (regexy). Začneme trochou
teorie a postupně si projdeme spoustu příkladů od těch jednodušších
až po ty složitější. Využijeme nástroje jako je grep, sed a editor
ViM, ale podíváme se i na další utility pro zpracování textu.
Tahák
GNU grep/sed
GNU grep -E
GNU awk
vim
Perl
znaky
libovolný znak (kromě \n)
.
.
.
.
.
množina znaků
[ ]
[ ]
[ ]
[ ]
[ ]
kromě těchto znaků
[^ ]
[^ ]
[^ ]
[^ ]
[^ ]
opakování
libovolný počet
*
*
*
*
*
alespoň jeden
\+
+
\+
\+
+
nanejvýš jeden
\?
?
\?
\?
?
přesně n×
\{n\}
{n}
{n}
\{n\}
{n}
minimálně
\{min,\}
{min,}
{min,}
\{min,\}
{min,}
min až max
\{min,max\}
{min,max}
{min,max}
\{min,max\}
{min,max}
pozice
začátek řádku
^
^
^
^
^
konec řádku
$
$
$
$
$
začátek slova
\<
\<
\< (\y)
\<
\A (\b)
konec slova
\>
\>
\> (\y)
\>
\z (\b)
závorky a paměť
skupina se zapamatováním
\( \)
( )
( )
\( \)
( )
třetí zapamatovaný
\3
\3
\3
\3
\3
Meta
POSIX
Unicode
Množina
Popis
\d
[:digit:]
\p{IsDigit}
[0-9]
Číslice
\D
\P{IsDigit}
[^0-9]
Cokoliv mimo číslici
\s
[:space:]
\p{IsSpace}
[ \t\n\r\f]
Bílý znak
\S
\P{IsSpace}
[^ \t\n\r\f]
Cokoliv mimo bílého znaku
\w
[:word:]
\p{IsWord}
[a-zA-Z0-9_]
Znaky identifikátorů
\W
\P{IsWord}
[^a-zA-Z0-9_]
Cokoliv mimo znaků identifikátorů
\b
hranice slova, opakem je \B
[:alnum:]
\p{PosixAlnum}
[A-Za-z0-9]
Alfanumerické znaky
[:xdigit:]
\p{PosixXDigit}
[A-Fa-f0-9]
Hexadecimální čísla
[:print:]
\p{PosixPrint}
[\x20-\x7E]
Tisknutelné znaky
[:alpha:]
\p{PosixAlnum}
[A-Za-z]
Abecední znaky
0. Stáhněte si příklady, na kterých si vše vyzkoušíme.
V balíku regexp_workshop.tar.gz
naleznete textové soubory, které obsahují data na kterých budeme testovat
regulární výrazy. V příkazové řádce provedete stažení a rozbalení následovně:
wget http://bruxy.regnet.cz/linux/if2012/regex_workshop.tar.gz
tar xvzf regexp_workshop.tar.gz
ls -l priklady/
Soubor obce.txt obsahuje seznam všech obcí České republiky (k roku 2011). Na
každém z jeho řádku najdete následující údaje oddělené mezerou:
Počet obyvatel obce.
Souřadnice obce v UTM. Jde o dvě celá čísla (x y) oddělená mezerou.
Jméno obce. (Kódování souboru je UTF-8.)
1. Zjistěte počet obcí České republiky.
wc -l obce.txt
2. Do samostatného souboru jmena.txt si uložte pouze názvy obcí.
-F nastavení oddělovače polí (přednastavený znak mezera)
-e perl nebude v argumentech hledat název skriptu, ale provede zadaný kód
while (<>) {
@F = split(' ');
print "@F[3]\n";
}
Proč je předchozí řešení s AWK a perlem chybné? V názvech obcí se
objevuje mezera a ta je zde ve významu oddělovače polí, víceslovné názvy se tedy
neuloží, protože vybíráme právě 4. sloupec a vše za tím ignorujeme.
Rozklíčování @F[3..$#F] – přístup k prvkům pole @F od 3 do
posledního ($#F). Perl na rozdíl od AWK indexuje pole od 0, v AWK znamená $0 celý
vstupní řádek.
3. Zjistěte TOP 10 názvů obcí.
sort jmena.txt | uniq -c | sort -nr | head -10
Jméma setřídímě (sort) a pomocí uniq zredukujeme opakující se řetězce,
uniq -c vloží jako prefix počet výskytů, ty si setřídíme sestupně (-r)
a nesmíme zapomenout sortu říct, aby řetězce třídil jako čísla (-n), vypsat
pouze "TOP 10" nám pomůže utilitka head.
Můžeme zkoušet zvyšovat čísla od 30 dále, trošku elegantnější řešení
jako skript pro bash:
IFS=$'\n';
for i in $(grep -E "^.{30,}$" jmena.txt)
do
echo ${#i} -- $i
done | sort -r
Na začátku nastavíme proměnnou prostředí IFS (Internal Field Separator),
oddělovač polí na znak nový řádek. Bash totiž bere jako oddělovač všechny bílé
znaky, takže cyklus for by pak bral jako jednotlivé položky samostatná slova,
ale mi potřebujeme celý řádek. Výpis echo vypisuje délku
řetězce ${#i} a pak obsah proměnné $i.
11. Testujeme správnost názvu proměnné (indentifikátor) v jazyce C.
Název
proměnné v jazyce C může obsahovat malé a velké znaky anglické abecedy,
podtržítko a číslo. Název proměnné nesmí začínat číslem.
.------------.
V |
>>-+-letter-+----+-letter-+-+----------------------------------><
'-_------' +-digit--+
'-_------'
Regulární výraz: ^[A-Za-z_][A-Za-z_0-9]*$
grep '^[A-Za-z_][A-Za-z_0-9]*$' promenne.txt
Vyzkoušejte grep s -v, tedy výpis řádků, které nevyhovují
regexpu, získáte tak neplatné názvy.
12. Čísla s plovoucí řadovou čárkou v jazyce C.
Čísla s plovoucí řadovou čárkou jsou reprezentována zápisem, který tvoří
celočíselná část, desetinná tečka, desetinná část, exponenciální část a
volitelný sufix. Příklady: 5.3876e4 (53.876), 4e-11 (0.00000000004),
1e+5 (100000), 7.321E-3 (0.007321), .8e3 (800), 2.0f (2.0).
Chyba v první verzi je záludná, protože celé číslo, např. 852 je také
vyhodnoceno jako literál plovoucích čísel, což ovšem není správně podle normy
jazyka C, překladač taková čísla bere vždy jako celá čísla a podle toho upravuje
další výrazy. Lexikální analyzátor našeho překladače, by se tedy nechoval přesně
podle normy.
Grep -E vypíše adresy, které nevyhovují regexu (-v) a zároveň
číslo řádku (-n) na kterém byla nevyhovující adresa nalezena.
14. Opravte "uvozovky" podle typografických „pravidel“.
Častá typografickou chybou je použití znaku pro palcovou míru
" ve významu uvozovek. Uvozovky můžeme zapisovat přímo jako
znaky UTF-8, kde uvozující znak je „ „ (U+8222).
a koncový znak “ je “ (U+8220).
Náhrada pomocí sed skriptu:
sed -e 's/"\(.*\)"/„\1“/g' uvozovky.txt (chyba!)
sed -e 's/"\([^"]*\)"/„\1“/g' uvozovky.txt
Potřebujeme vzít slovo mezi "uvozovkami" a to vložit mezi správné znaky.
Regexy jsou rozežrané, takže pokud označujeme všechno, co se nachází mezi dvěma
", bere se vše mezi prvním a posledním výskytem. V řádku, kde je víc slov v
uvozovce, to tedy nedopadne vůbec podle našich představ. Regex je tedy nutné
zadat tak, že vyloučíme znak " z množiny sbíraných znaků.
Skupinu v \(závorkách\) si sed zapamatuje a můžeme jí
zpětně referencovat pomocí \1.