Kapitel 10 Reguljära uttryck och textbehandling II Detta är en fortsättning från kapitel 8 där reguljära uttryck och kommandot egrep introducerades. 10.1 Olika sorters reguljära uttryck Reguljära uttryck används inte bara av egrep utan dyker upp i flera sammanhang. Av historiska skäl används dock tyvärr flera olika varianter av reguljära uttryck. De som ni har fått lära er kallas utökade (extended) reguljära uttryck och har fler metatecken än de tidigare enkla (basic) reguljära uttrycken.) När det finns funktioner i olika programspråk för att göra saker med reguljära uttryck så är det i allmänhet utökade reguljära uttryck som gäller, och det är skälet till att vi här har tagit upp dessa som de normala. Programmet egrep är egentligen en variant av ett program grep. (Det första e-et står för extended.) Om ni har slagit upp man-sidan för fgrep eller egrep så har ni märkt att det är samma man-sida för grep, egrep och fgrep. Och i Info hittar man bara ett uppslag grep där de alla beskrivs. Det är alltså grep som är originalet. Det finns väljare till grep för att få det att söka efter en fix sträng eller efter ett utökat reguljärt uttryck, och fgrep och egrep är variantprogram där en sådan väljare är underförstådd. (En del program som använder reguljära uttryck har dessutom sina egna utökningar av dem. Det har t. ex. Emacs och programspråket Perl. Flera utökningar som har gjorts för Perl har plockats över till andra sammanhang också, t. ex. programspråken Java och Python.) 10.1.1 Reguljära uttryck i Emacs Det finns flera kommandon i Emacs som använder reguljära uttryck. Vid sidan av C-s och C-r som söker framåt respektive bakåt efter en textsträng så finns även C-M-s och C-M-r som söker framåt respektive bakåt efter ett reguljärt uttryck. (Här ska alltså både Control och Meta användas tillsammans.) Under en inkrementell sökning kan man också använda M-r för att slå om huruvida sökningen ska gälla reguljära uttryck eller inte. M-x list-matching-lines är ett kommando som listar alla rader i aktuell buffert som innehåller något som matchar ett reguljärt uttryck. I Emacs används dock reguljära uttryck där inte alla metatecken som ni har lärt er är speciella. Vill man använda {} () där så måste man skriva bakstreck framför. Så t. ex. det som ni annars skulle skriva de (sju 7) dvärgarna måste i Emacs skrivas de \(sju\ 7\) dvärgarna. 10.2 Grep och rader Som vi har sett så tar grep-kommandona fram hela de rader som innehåller det man söker efter. Det är i stil med hur de flesta Unix-kommandon som hanterar textfiler är radorienterade. Med väljaren -o (--only-matching) så visas däremot bara den del som verkligen matchade ett uttryck. Se figur 10.1 där ett reguljärt uttryck som matchar ord med bara versaler som börjar med B används. Först tas alla rader som innehåller sådana ord fram. Det är sju stycken. Sedan bara själva orden, vilket är åtta stycken, eftersom det finns två sådana på den första hittade raden. 107
10. REGULJÄRA UTTRYCK OCH TEXTBEHANDLING II $ egrep '\<B[[:upper:]]*\>' wonderland.txt 'Well, I've tried to say "HOW DOTH THE LITTLE BUSY BEE," but it 'It was the BEST butter,' the March Hare meekly replied. was the BEST butter, you know.' 'IT DOES THE BOOTS AND SHOES.' the Gryphon replied very 'Oh, I BEG your pardon!' she exclaimed in a tone of great over the verses to himself: '"WE KNOW IT TO BE TRUE--" that's Then again--"before SHE HAD THIS FIT--" you never had fits, my $ egrep -o '\<B[[:upper:]]*\>' wonderland.txt BUSY BEE BEST BEST BOOTS BEG BE BEFORE Figur 10.1: Exempel på väljaren -o. Det reguljära uttrycket betyder ett ord med bara versaler som börjar med B. 10.3 Girighet och hunger Ett sådant uttryck som [1-9]0+ matchar ju en siffra 1 9 följt av en eller flera nollor, t. ex.»10«eller»30000«. $ echo 'Timmy 2000' egrep -o '[1-9]0+' 2000 I exemplet ovan ser vi hur uttrycket matchade»2000«. Men det skulle ju också kunna ha matchat bara»200«eller»20«. Ändå är det just matchningen»2000«som vi fick fram. Vid matchningen väljs den längsta av flera möjligheter som börjar på samma plats. Man säger att operatorn + här är girig eller hungrig (greedy på engelska) vilket innebär att den»vill«matcha så mycket som möjligt. Samma gäller de andra repetitionsoperatorerna vi har tagit upp. Detta behöver man tänka på ibland. Om man t. ex. vill söka efter citat inom citattecken kan det ligga nära till hands att använda ett reguljärt uttryck ".*" för att visa att det ska börja och sluta med " och däremellan får innehålla vad som helst. Men om den rad man söker på lyder så här: Ska det stå "i dag" eller "idag"? så hittar man då hela texten "i dag" eller "idag" Den börjar ju och slutar med citattecken och är längre än bara "i dag" som kanske var det man var ute efter. I detta fall är ett sätt att lösa problemet att kräva att tecknen mellan de båda citattecknen inte själva får vara citattecken, dvs. att använda [^"] istället för. för dessa tecken. Jämför följande: $ egrep -o '".*"' testfil "i dag" eller "idag" $ egrep -o '"[^"]*"' testfil "i dag" "idag" (Här används enkla citattecken '...' runt argumenten i skalet, medan de inre dubbla citattecknen "..." ingår i det reguljära uttrycket.) 10.4 Bakåtsyftning: \1 \2... \9 Om man vill kunna matcha (bara) någon av följande meningar 108
sed En gul stol är gul. En blå stol är blå. så duger det inte med En (gul blå) stol är (gul blå). för det skulle även matcha»en gul stol är blå.«och»en blå stol är gul.«. I de båda valmöjligheterna kan båda valen väljas oavsett vad som sker i det andra valet. Istället kan man skriva En (gul blå) stol är \1. med tillbakasyftning på det som stod inom parentesen. Med bakstreck följt av en siffra kan man nämligen syfta tillbaka till en tidigare del av ett reguljärt uttryck, nämligen en del som stått inom parentes. Med ([0-9])\1\1 matchar man t. ex. tre likadana siffror i rad, t. ex.»444«eller»000«, och med (.).\1 kan man matcha tre tecken vilka som helst där det första och det sista tecknet är likadana. Här skrevs parenteserna till bara för att vi skulle kunna referera tillbaka till den delen. Om man har flera delar inom parentes kan man hänvisa till dem med \1, \2, \3 osv. Så ([[:upper:]])([[:upper:]])\2\1 matchar två versaler som följs av varandra i omvänd form, t. ex.»noon«eller»abba«. 10.5 sed Med kommandot sed kan man göra en mängd olika omvandlingar av text. Namnet står för stream editor. Det kan göra mycket som en editor kan göra, men gör det inte interaktivt utan genom att bearbeta text som kommer in i en inputström. Detta kan läggas in som ett avancerat filter som läser text från standard input och skriver ut en editerad version av texten på standard output.»reguljära«uttryck: Beteckningen reguljära uttryck är ursprungligen en fackterm inom automatateori och reguljära uttryck i olika Unix-program började som något som direkt efterliknade detta. Med tillägg som bakåtsyftningar är resultat dock inte längre reguljärt enligt den betydelse den termen har i automatateori, men i praktiken fortsätter man att kalla dessa mönster för reguljära uttryck ändå. Kommandot sed är lite som en schweizisk armékniv som kan utföra en mängd olika saker, bland annat sådant som ni har använt andra kommandon till, som head, tail och tr. Här kommer vi dock att bara använda detta kraftfulla verktyg till en specifik sak eftersom det inte finns nåt mer specifikt verkyg till just detta i Unix-verktygslådan. Det är att byta ut text. 10.5.1 Att ersätta text med sed Tidigare har vi använt tr för att byta ut text, men det är begränsat genom att det bara är enstaka tecken som hanteras. Det duger inte om vi t. ex. vill byta ut alla förekomster av foo mot bar. Dessutom har det begränsningar vid hantering av flerbytestecken. Eftersom ni nu bara kommer att använda sed till en sak så kommer alla era anrop av sed att likna varandra. De kommer alla att se ut ungefär som sed -r 's/regexp/replacement/g' Väljaren -r till sed gör att utökade reguljära uttryck ska användas. Eftersom dessa kan ses som det normala numer så kan ni gott använda detta genomgående. Argumentet till sed talar om vilken editering som ska utföras. Det s argumentet börjar med här är ett kommando i sed för substitute. Det söker upp förekomster av text som matchar det reguljära uttrycket regexp och byter ut dessa mot texten replacement. Så t. ex. kan man skriva sed -r 's/color/colour/g' ed och sed: Den ursprungliga editorn på Unix, som användes redan i forntiden före bildskärmarnas tid, hette ed och var skriven av Ken Thompson. Det var Thompson som först introducerade reguljära uttryck i editorer. När kommandot sed kom några år senare som en variant att använda för icke-interaktiv editering av en inputström så byggde därför dess kommandon på motsvarande kommandon i ed. Det enda sed-kommando vi tar upp här, s, finns t. ex. även i ed. Efter bildskärmarnas intåg blev vi (= visual) vanligare i Unix. Den skrevs av Bill Joy för BSD. 109
10. REGULJÄRA UTTRYCK OCH TEXTBEHANDLING II för att byta ut alla»color«mot»colour«eller sed -r 's/s\>/z/g' för att byta ut varje s som står i slutet av ett ord mot z. Man brukar normalt använda snedstreck (/) som här för att avdela argumentet, men det kan vara något annat tecken istället, bara det är samma tecken på alla tre ställen. Så om man behöver snedstreck i sitt reguljära uttryck eller i ersättningen får man lov att välja nåt annat, t. ex. sed -r 's!1/2!½!g' för att byta alla förekomster av de tre tecknen»1/2«mot det ensamma tecknet»½«. Efter det sista snedstrecket står väljare till s- kommandot. I exemplen ovan är det g som gör att alla förekomster byts ut (global). Annars byts bara den första förekomsten på varje rad ut. En annan väljare är i som ignorerar skillnader mellan versaler och gemener. Jämför följande fyra exempel som bara skiljer sig i vilka av dessa väljare som har använts. sed -r 's/[[:punct:]]/&\n/g' där ersättningsdelen betyder det som [[:punct:]] matchade följt av ett nyradstecken (vilket skrivs \n här precis som t. ex. i tr). Följande exempel dubblerar vokaler genom att använda ampersand två gånger. sed -r 's/[aeiouyåäö]/&&/gi' En annan specialare är att man i ersättningstexten kan lägga in \1 osv. för att bara lägga in en del av ett reguljärt uttryck, motsvarande vad detta betyder inne i de reguljära uttrycken. Exempel: sed -r 's/"([0-9]+)"/<\1>/g' Detta byter ut "" runt ett sifferuttryck mot <> så att t. ex. "031" blir <031>. Eftersom [0-9]+ står inom parentes i det reguljära uttrycket så är det det som \1 syftar tillbaka på. $ cat jaha.txt Procent procent procent PROCENT $ sed -r 's/procent/%/' <jaha.txt Procent % procent PROCENT $ sed -r 's/procent/%/g' <jaha.txt Procent % % PROCENT $ sed -r 's/procent/%/i' <jaha.txt % procent procent PROCENT $ sed -r 's/procent/%/gi' <jaha.txt % % % % 10.5.2 Att använda den gamla texten I sed -r 's/regexp/replacement/g' är det bara regexp som är ett reguljärt uttryck. Det är detta som ska matcha någon del av inputten. T. ex. asterisker betyder inte något särskilt i replacement utan de läggs in som de är som ersättning. Men ett par specialare finns även i ersättningstexten. Tecknet & (ampersand) i ersättningstexten betyder hela den text som matchade det reguljära uttrycket. Om man vill bryta raden efter varje interpunktionstecken kan man därför göra Varför heter det grep?: I editorn ed användes reguljära uttryck inte bara med kommandot s, utan även med bl. a. ett kommando g med vilket man sökte upp rader som matchade ett visst reguljärt uttryck och gjorde nåt med dessa rader. För att ta bort alla rader som innehåller»foo«kunde man t. ex. göra g/foo/d där d är ett kommando som tar bort en rad. För att skriva ut alla rader som matchade ett reguljärt uttryck använde man istället p (= print). Ibland förkortas regular expression med re, så en sådan operation kunde beskrivas g/re/p vilket är skälet till att det senare skrivna program som utförde just den operationen fick namnet grep. 110
Laboration 10: Reguljära uttryck II Redovisning Skicka svar på frågorna till Per senast fredag 25/11. Frågorna är samlade i olika teman snarare än att vara sorterade från lätta till svåra. När frågan är i stil med»vilka ord finns det som...?«så ge dels svar på frågan och tala dels om vilket kommando eller vilka kommandon du använt för att få fram svaret. I flera frågor ska du ge ett kommando som åstadkommer en viss förändring, men det finns ingen given fil som detta ska göras i. Då räcker det att svara med ett kommando. Svenska ord I /local/dict/dsso/ord.txt finns en stor samling svenska ord av blandad kvalitet. Använd den för frågorna i detta avsnitt. 1 Eftersom filen bara innehåller ord med ett ord per rad kan du använda ^ och $ för ankring till ordbörjan och ordslut och. för ett godtyckligt tecken inne i ordet. Uppgift 10.1 Hur många ord finns det som slutar på bokstaven f? Uppgift 10.2 Hur många ord finns det som innehåller»stp«i sig (som t. ex.»hästpolo«) och hur långt är det längsta av dessa ord? Uppgift 10.3 Vilka ord har f som näst första tecken och p som näst sista tecken? Uppgift 10.4 Vilka ord med minst nio bokstäver går att skriva enbart med tecknen på översta bokstavsraden på tangentbordet? (qwertyuiopå) 1 Detta kommer från Den stora svenska ordlistan på http: //dsso.se/ ett försök att göra en fri svensk ordlista över nätet. Detf verkar dock vara delvis avsomnat för tillfället. Med T9-kodning på mobiltelefoner kodas abcåä som 2, def som 3, ghi som 4, jkl som 5, mnoö som 6, pqrs som 7, tuv som 8 och wxyz. Uppgift 10.5 (T9) Vad finns det för ord med T9- kodningen»3677527«? Uppgift 10.6 (Monoton) Vad finns det för ord som är minst 16 tecken långa vars enda vokal är o? Ordet atombomba innehåller texten»omb«två gånger i rad: at-omb-omb-a. Uppgift 10.7 Vad finns det för ord som innehåller en text med fem tecken två gånger i rad? Uppgift 10.8 Hur många ord finns det som liksom farfar och kaka består av två likadana hälfter? Uppgift 10.9 Vad finns det för sjubokstavsord som är palindrom? (Palindrom är sådana texter som lyder likadant framlänges som baklänges, t. ex. kajak.) Förkorta Uppgift 10.10 Här ges ett antal reguljära uttryck som alla går att skriva kortare så att ändå exakt samma texter matchas. Hur? a) bar{1,} b) [[:alnum:]]{0,1} c) (f){1}[o]{2} d) ((kan)(a e y)l) e) (måndag tisdag) kväll Väljaren -o I dessa uppgifter ska du använda väljaren -o för att bara få ut det som matchade ett uttryck från en fil. 111
LABORATION 10: REGULJÄRA UTTRYCK II Uppgift 10.11 Vad finns det för ord med exakt 22 tecken i uppror.txt? Använd \w för att beteckna ett tecken inne i ett ord. Uppgift 10.12 Vilka ord i uppror.txt börjar med»flä«? Uppgift 10.13 Ange vilka sifferuttryck som finns i filen GNU i din kurskatalog. Sortera dem i nummerordning. Uppgift 10.14 Gör om uppgift 8.38 med korta Strindberg-ord med hjälp av egrep -o för att plocka ut orden. Uppgift 10.15 Vilka tecken är vanligast som förstatecken i orden i uppror.txt? Gör en tjugo-i-topplista! Uppgift 10.16 Vad finns det för ord med minst tre vokaler i rad i uppror.txt? Uppgift 10.17 Vad finns det för parentesuttryck i uppror.txt? På en rad står det t. ex. försvar (och anfall) för att bevara den bästa av Alltså ska (och anfall) ingå i ditt svar. En del parenteser börjar på en rad och slutar på en annan. Dem kan du för enkelhets skull strunta i. Bonusuppgift (överkurs): Ta hand om sådana fall också, men ta då ändå bara med sådana med högst 50 tecken inom parenteserna. Uppgift 10.18 Ta med egrep -o fram telefonnummer med riktnummer från tidningstexterna i /local/kurs/ids/ivt2-svenska.txt. Din lösning kan vara mer eller mindre bra. Ju fler korrekta nummer du får ut desto bättre, men undvik att få falska träffar med sifferuttryck som inte är telefonnummer. Ta sed dit man kommer Uppgift 10.19 (Stavningsreform) Skriv ett kommando som byter ut alla»ck«mot»kk«. Uppgift 10.20 Om Strindberg hade behövt skriva Roeda rummet på en engelsk skrivmaskin och fått lov att skriva aa ae oe istället för å ä ö, hur hade då början av den lydit? Uppgift 10.21 Byt ut alla»ej«och»icke«mot»inte«i en text. Se till att du bara byter ut hela ord så att du inte råkar byta t. ex.»mejl«mot»mintel«eller»kicken«mot»kinten«. Uppgift 10.22 I en text används snedstreck som ett slags parenteser, /så här/. Byt ut dessa mot hakparenteser, [så här]. Uppgift 10.23 En text använder decimalpunkt istället för decimalkomma, dvs. där står t. ex.»3.14«istället för»3,14«. Byt till komma! (Men se till att bara byta ut sådana punkter som står mellan två siffror!) Uppgift 10.24 I en text står prisuppgifter som»150$«med dollartecknet efter siffrorna. Skriv ett kommando som ändrar detta till»$150«eller mer precis flyttar ett dollartecken som står efter en siffersekvens till att istället stå före denna siffersekvens. Mer stavningsreformer Uppgift 10.25 Skriv ett kommando som byter ut c mot s om det följs av e, i eller y. Uppgift 10.26 Byt ut alla dubbeltecknade bokstäver mot enkeltecknade, så»äggkopp«ska bli»ägkop«. Rorövovarore Om du har läst Astrid Lindgrens böcker om Kalle Blomkvist så känner du till rövarspråket. Det är ett hemligt språk som barnen i de böckerna använder där varje konsonant ersätts med två likadana konsonanter med ett o mellan sig. Ordet detektiv blir t. ex. dodetotekoktotivov. Uppgift 10.27 Skriv ett sed-kommando som omvandlar sin input till rövarspråket! 112
Överkursuppgifter Du ska alltså omvandla t. ex.»t«till»tot«. Versaler ställer till med en komplikation. Allra helst vill man att»per«ska bli»poperor«, men gör istället en lösning som ger»poperor«vilket är helt OK! Bonusuppgift (överkurs): Läs på i Info om sed för att se hur du med s-kommandot kan lösa versalproblemet och gör så. I Emacs Öppna i Emacs filen /local/kurs/ids/ THE-GNU-PROJECT och använd där kommandot list-matching-lines för att lista rader med match på [0-9]+ (sifferuttryck). (Detta kommando finns inte i menyerna.) Uppgift 10.28 Hur många matchingar blev det och hur visas dessa? Uppgift 10.29 Sökträffarna visas i en buffert som är i en särskild mode. Ta fram beskrivningen för den. Vad står där? (Återge de inledande raderna innan alla snabbkommandon som är specifika för den listas.) Uppgift 10.32 Ge en frekvenslista över ord som kommer efter ordet to i /local/kurs/ids/ THE-GNU-PROJECT. Listan ska alltså visa hur ofta olika ord förekommer direkt efter to. På rad 11 står det t. ex.»... limited to our particular... «vilket ger en första förekomst för ordet our. Inga skiljetecken ska komma emellan, utan bara ett mellanrum. Du kan strunta i sådana fall där to står sist på en rad. Smeknamn En del smeknamn på pojknamn följer mönstret i tabell 10.1. Vi kan för enkelhets skull anta att den dubblerade konsonant alltid är den sista konsonanten i de konsonantgruppen som följer efter vokalen. (Dvs. att»ludvig«skulle bli»luvve«istället för det egentligen bättre»ludde«.) Uppgift 10.33 Skriv ett sed-kommando som gör omvandlingar enligt denna princip om det får ett pojknamn som input. Överkursuppgifter Resten är frivilliga överkursuppgifter, men ge dig gärna på några av dem i alla fall. Lite svar här kan uppväga missar på de tidigare uppgifterna. Uppgift 10.30 Skriv ett sed-kommando som tar bort alla attribut och värden från en XML-fil. Input som <programme lang="en" also_sent="2007-12-24"> ska t. ex. bli <programme> Du kan utgå från att alla värden bara har alfanumeriska tecken i sig. Uppgift 10.31 (Monoton2) Vad finns det för ord med minst 16 tecken där alla vokaler är likadana? Meningen är att göra en lösning som funkar för alla vokaler, så att du inte behöver ge ett kommando för varje vokal. Tabell 10.1: Några smeknamn på svenska pojknamn Original Smeknamn Frans Frasse Hans Hasse Karl Kalle Leif Leffe Nils Nisse Olof Olle Stefan Steffe Urban Ubbe 113