9. december

grep(1)

I dag handler julekalenderen om programmet grep. Grep bruges til at sorterer de rigtige oplysninger ud af filer, så man kun ser det man er interesseret i. Det kunne for eksempel være bestemte linjer i en log-fil.

Hvad det er man er interesseret i angiver man med et regulært udtryk. Dette er beskrevet lidt mere detaljeret tilsidst. I begyndelsen leder vi bare efter bestemte strenge.

Følgende er en række linjer fra min apachelog:

212.242.196.94 - - [02/Dec/2001:07:41:12 +0100] "GET /test/alletest/etagesenge/?print=1 HTTP/1.1" 200 2283
212.242.196.94 - - [02/Dec/2001:07:41:12 +0100] "GET /mad/slankemad/slankemadtilmandfolk/links/ HTTP/1.1" 302 321
212.242.196.94 - - [02/Dec/2001:07:41:12 +0100] "GET /mad/slankemad/slankemadtilmandfolk/doc015/ HTTP/1.1" 200 18696
212.242.196.94 - - [02/Dec/2001:07:41:13 +0100] "GET /hjemmet/boligen/boligensmaterialer/trae2/mere_information/ HTTP/1.1" 200 18479
212.242.196.94 - - [02/Dec/2001:07:41:14 +0100] "GET /hjemmet/boligen/boligensmaterialer/trae2/leksikon/ HTTP/1.1" 200 29473

Jeg kan nu være interesseret i hvor mange gange nogle har besøgt sider hvis urler begynder med /mad. Det gøre jeg med følgende komando:

$ grep "GET /mad" /var/log/apache/access_log

Dette vil skrive netop de linjer ud hvori strengen "GET /mad" indgår. Det kan også være at jeg bare er interesseret i antallet, så giver jeg lige grep flaget -c, så tæller den bare hvor mangle linjer det matcher:

$ grep -c "GET /mad" /var/log/apache/access_log

Måske er jeg netop intereseret i linjer der ikke drejer sig om mad. Med flaget -v kan jeg få grep til at finde linjer der ikke indeholder strengen:

$ grep -v "GET /mad" /var/log/apache/access_log

Hvis man ikke angiver noget filnavn så læser grep bare fra STDIN. Det gør grep let at bruge med pipelines, hvor uddata fra en kommando ledes videre og bruges som inddata for en anden kommando. Det så vi allerede et eksempel på i går.

Man kan også grep'e i flere filer ad gangen, så skriver man bare flere filnavne på komandolinjen. Grep vil så være så flink at skrive hvilken fil den har fundet resultatet i (dette kan slås fra med -h).

Hvis man har en masse tekstfiler og vil finde hvor der står noget om Linux kan man let skrive:

$ grep Linux *.txt

Men det kan så ofte være svært at afgøre om man har fundet noget interessant. Ofte vil man have behov for noget kontekst. Hvis man istedet skriver

$ grep -C 3 Linux *.txt

vil grep skrive 3 linjer ud før og efter den linje der står Linux på. Man kan også med -A og -B nøjes med at få linjer der kommer henholdsvis efter (after) eller før (before) den linje der matcher.

Er man bare interesseret i at vide hvilke filer, der siger noget om Linux, så kan man kalde grep med -l. Så vil grep bare skrive filnavnene ud på de filer, der indeholder ordet Linux.

Hvis man ønsker at kikke en hel katalogstruktur igennem, så kan grep også prøve alle katalogr rekursivt, dette gøres med -r. Ofte vil man dog se folk bruge find og xargs (beskrevet 2. December) til dette.

Hvis man skal finde hvor grep fandt en linje, kan det være meget rart at få skrevet linjenummeret ud også. Det får man grep til med flaget -n.

Regulære udtryk

Regulære udtryk (engelsk 'regular expressions' eller 'regexp') er en måde at angive tegnmønstre. Regulære udtryk er ofte sammensatte af mindre udtryk.

De mest simple regulære udtryk består kun af et enkelt tegn og matcher strenge der netop består af et tegn. a matcher for eksempel netop strengen "a" og b matcher netop strengen "b".

To regulære udtryk kan sættes efter hinanden. Resultatet matcher så strenge, der først matcher det første udtryk og det andet udtryk matcher så resten af strengen. For eksempel består det regulære udtryk ab af de to udtryk a og b sat sammen og matcher netop strengen "ab". På samme måde kan man sætte flere udtryk sammen og få hele ord abekat matcher netop strengen "abekat".

To regulære udtryk kan sættes sammen med en lodret streg imellem. Resultatet matcher strenge der enten matcher det første udtryk eller det andet udtryk. Vi kan for eksempel skrive a|b, der matcher både strengen "a" og strengen "b".

Så kommer problemet, hvordan er ab|e sat sammen? Enten kan det opfattes som "ab eller e", der matcher strengene "ab" og "e" eller også kan det opfattes som "a efterfulgt af b|e", der matcher strengene "ab" og "ae". Man har valgt at sammensætning binder stærkere sammen end eller-stregen, altså er betyder det "ab eller e". Ligesom med normale regnestyker kan vi sætte nogle parenteser ind vi kan altså skrive "a efterfulgt af b|e" som a(b|t).

Hvad nu hvis vi ønsker at matche et "a" efterfulgt af en række b'er efterfulgt af et "a"? Vi kan let skrive "a" efterfulgt af.... Men hvordan skriver vi en række "b"'er?

Vi kunne prøve med a(b|bb|bbb|bbbb)a, men hvor langt skal vi blive ved? Det ville være meget lettere hvis vi direkte kune skrive "en række b'er", og det kan vi. Det regulære udtryk b+ matcher et eller flere b'er. Vores a efterfulgt af en række b'er efterfulgt a et a kan altså skrives som ab+a.

Nu vil en række computerfolk sige at den tomme række også er en række b'er. Altså at strengen "aa" også er et a efterfulgt af en række b'er efterfulgt af et a. Rækken er bare tom. Derfor er det regulære udtryk b* mere brugt. Det betyder nemlig nul eller flere gange.

Tilsidst så kan vi være intereseret i at noget måske er i strengen. Altså vi vile matche enten "aa" og "aba". Det gør vi på følgende måde ab?a. Det regulære udtryk b? matcher altså enten ingen b'er eller netop et b.

Opsumering

Så er det vist tid til en opsummering:

a
Matcher netop et "a"
ab
Matcher et "a" efterfulgt af et "b"
a|b
Matcher et "a" eller et "b"
a?
Matcher et a eller ingenting.
a*
Matcher nul eller flere a'er
a+
Matcher et eller flere a'er

Forkortelser og specielle tegn

Forestil dig at du ønsker at matche ord der begynder med et stort bogstav. Altså et stort bogstav efterfulgt af en række små bogstaver. Det skulle nu være let nok at se at det gøres med følgende udtryk:

(A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|X|Y|Z)(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|x|y|z)+

Det var langt. Det kan heldigvis forkortes til [A-Z][a-z]+. En liste af tegn i kantede parenteser matcher nemlig netop et af tegnene. Man kan enten skrive hele listen ud eller sige at man skal bruge fra et stort A til et stort Z. Hvis det første tegn i listen er et ^, så matches alle tegn der ikke er i listen.

Så er der tre specielle tegn det også er meget godt at bruge . matcher alle tegn, ^ matcher starten af linjen og $ matcher slutningen af linjen.

Hvis man ønsker at matche på nogle af de tegn, der har speciel betydning, det vil sige ovenstående tegn, parenteser, plus, minus, stjerne og lodret streg, skal man lige skrive en \ foran, altså \. for at matche et punktum.