Hva et mønster er
Et regulært uttrykk, regex for kort, er en søkespesifikasjon som brukes én linje om gangen: grep leser en linje, spør om mønsteret matcher et sted i den, og skriver ut linjen hvis den gjør det. Kapittelet Tekstbehandling satte grep og sed i arbeid med bokstavelige strenger. Denne guiden er grammatikken under, så treff skjer med vilje i stedet for ved flaks:
$ printf '%s\n' cat cut c-t coat ct category 'a cat category' > words.txt
$ grep 'cat' words.txt
cat
category
a cat category
Mønsteret er de tre bokstavene c-a-t, og alle linjer som inneholder dem
kom tilbake. Hver linje testes etter tur, og enten kvalifiserer den,
eller så gjør den ikke det. Den første printf '%s\n'-kommandoen
skriver hvert argument som sin egen linje, og den formen er
testoppsettet for hele guiden.
Én advarsel først. Leserne kommer trenet på filnavnsglobber, som
kapittelet Shell-grunnleggende
tar for seg, og de to språkene deler tegn, men ikke betydninger. I en
glob er * en vilkårlig streng og ? nøyaktig ett vilkårlig tegn. I et
regex er * null eller flere av elementet foran, og ? vil bety at
elementet er valgfritt. Alt nedenfor er sjekket på Ubuntu 26.04,
Fedora 44 og Arch, med byte for byte identisk utdata.
Bokstavelige tegn og metategn
Bokstaver og sifre er bokstavelige, og grep jakter på dem som ren tekst.
Noen få tegn har derimot bivirker: punktum ., hakeparenteser [ ],
cirkumfleks ^, dollartegn $, stjerne * og omvendt skråstrek \,
med + ? ( ) | { } som slutter seg til når vi skifter dialekt. Punktummet
matcher ett vilkårlig tegn:
$ printf '%s\n' a.b axb 'a b' a-b > litdot.txt
$ grep 'a.b' litdot.txt
a.b
axb
a b
a-b
Punktummet stakk inn for en x, et mellomrom, en bindestrek og til slutt for seg selv. Den siste vendingen er vanen denne guiden egentlig handler om: før du stoler på et mønster med tegnsetting i seg, spør tegn for tegn om hver av dem er ment bokstavelig. Regn tegnsetting som skyldig til det er bevist at den er ment bokstavelig. Alle mønsterne nedenfor bærer anførselstegn, og anførselstegn-delen viser hvorfor. Dialekt-delen har i tillegg et flagg som slår av alle metategn.
Ankre
^ fester et mønster til starten av linjen, $ til slutten, og de to
sammen beskriver en tom linje:
$ printf '%s\n' '2026-09-21 ERROR: disk full' 'WARNING: low memory' '2026-09-20 INFO: backup done' 'ERROR: timeout' '2026-09-19 INFO: cleanup ok' > date.log
$ grep '^ERROR' date.log
ERROR: timeout
$ printf '%s\n' box fox oxen boxy > endx.txt
$ grep 'x$' endx.txt
box
fox
$ printf '%s\n' 'line one' '' 'line two' '' '' 'line three' > blanks.txt
$ grep -c '^$' blanks.txt
3
Én annen logglinje inneholder ERROR, men ikke forrest, og oxen og
boxy bærer x-en sin et annet sted enn på slutten. ^$ telte til 3
fordi -c bytter de matchende linjene ut mot en telling av dem.
Tegnklasser
Hakeparenteser matcher ett tegn, men lar deg stemme over hvilket:
[abc] er a, b eller c, ett enkelt tegn hentet fra lista:
$ printf '%s\n' abc abx xyz > negset.txt
$ LC_ALL=C grep '[^abc]' negset.txt
abx
xyz
[^abc] snur avstemningen til et vilkårlig tegn unntatt a, b eller c,
så den rene abc-linjen består utelukkende av utelatte tegn og faller
ut, mens abx overlever på styrken av sin x. En bindestrek inni
hakeparentesene lager et område: [0-9] er et vilkårlig siffer, [a-z]
en vilkårlig liten bokstav, og begge gjør ekte arbeid senere i guiden.
Områder oppfører seg ulikt avhengig av locale, så eksempelet over kjører
LC_ALL=C grep ... for å fastlåse den enkle POSIX-standarden. De
vanlige tegnmengdene har også navngitte klasser med doble
hakeparenteser, der de ytre parentesene er del av skrivemåten:
[[:digit:]] fanger sifre, [[:upper:]] store bokstaver, [[:space:]]
blanktegn, og de navngitte formene går utenom de locale-spørsmålene
områder kan reise. Gi [[:upper:]] en fil med både store og små
bokstaver, og den beholder bare linjen som har en stor bokstav i seg:
$ printf '%s\n' ABC abc > classes.txt
$ grep '[[:upper:]]' classes.txt
ABC
Kvantifikatorer, del én: stjernen
* fester seg til elementet foran og gjentar det null eller flere
ganger. Nullen er delen som overrasker folk, og setter du sammen
punktum og stjernen til .*, får du en vilkårlig rekke av hva som
helst:
$ printf '%s\n' ac abc abbc 'ab+c' xyz > quant.txt
$ grep 'ab*c' quant.txt
ac
abc
abbc
$ grep 'a.*c' quant.txt
ac
abc
abbc
ab+c
$ printf '<a><b>\n' | grep -o '<.*>'
<a><b>
ab*c leses: a, deretter et vilkårlig antall b, også null av dem,
deretter c, og det er derfor ac matcher uten én eneste b. Stjernen
binder seg til det enkelte elementet foran seg, så det er b som
gjentas, aldri hele ab. Linjen ab+c slipper også gjennom under
a.*c: den begynner med en ekte a, ender med en ekte c, og .* dekker
glatt +-en i mellom. Samme appetitt er grådighet. Den siste
kommandoen brukte -o, som skriver ut hvert treff på sin egen linje i
stedet for hele linjen, og treffet startet ved den første < og endte
ved den siste >, med begge taggene innenfor. <a> alene ville
tilfredsstilt mønsteret, men en grådig stjerne sikter på det lengste
mulige løpet og gir tegn tilbake først når resten av mønsteret tvinger
den til det.
BRE og ERE, de to dialektene
Nå kommer fellen alle regex-lærlinger går i. Si at du vil ha én eller flere b og skriver det på den opplagte måten, så svarer grep med den eneste linjen som inneholder de fire tegnene som bokstavelig tekst:
$ grep 'ab+c' quant.txt
ab+c
$ grep -E 'ab+c' quant.txt
abc
abbc
$ printf '%s\n' color colour colouur > colr.txt
$ grep -E 'colou?r' colr.txt
color
colour
$ printf '%s\n' o oo ooo book root > oos.txt
$ grep -oE 'o{2,3}' oos.txt
oo
ooo
oo
oo
$ grep 'o{2,3}' oos.txt
grep feilet ikke på den første kommandoen. I standarddialekten, BRE for
basic regular expressions, er plusstegnet et vanlig tegn, og det samme
er spørsmålstegnet. Den utvidede dialekten, ERE, er der +, ?,
krøllparenteser, parenteser og pipetegnet blir aktive, og -E velger
den. Derfor fant den andre kommandoen én eller flere b. Den valgfrie u
får begge de kjente skrivemåtene av colour til å matche, mens colouur
matcher ingen av dem. Intervaller teller gjentakelser: {n} er
nøyaktig n, {n,} er n eller flere, {n,m} er et område, og o{2,3}
er to til tre o-er, så den ensomme o-en faller ut og -o viser
resten: hele ooo, deretter én oo fra hver av book og root.
Kommandoen med bare krøllparenteser like etterpå er fellen. I BRE
trenger krøllparentesene baklengs skråstrek, skrevet \{2,3\}, og den
bare formen er ikke en tolerert skrivemåte: på nåværende GNU grep,
3.12 på alle tre testsystemene, er bart o{2,3} helt og holdent
vanlige tegn. Den søkte i stillhet etter selve den teksten, fant den
på ingen linje, skrev ut ingenting og avsluttet med exit status 1. Når
et mønster med krøllparenteser ikke finner noe, sjekk dialekten før du
skylder på dataene. Gi krøllparentesene sine baklengs skråstreker, og
det samme søket virker i vanlig BRE, med hele linjer i utskriften
denne gangen fordi -o mangler:
$ grep 'o\{2,3\}' oos.txt
oo
ooo
book
root
Og når målet er et mønster uten magi i det hele tatt, behandler -F
hele innholdet som bokstavelig tekst:
$ grep -F 'a.b' litdot.txt
a.b
Punktumsgåten fra tidligere ender der. Mellom -E og -F er det
vanlig BRE som gjenstår: bokstavelige tegn, klasser, ankre og stjernen,
pluss skrivemåtene med baklengs skråstrek, intervallformen \{2,3\}
vist ovenfor og gruppene og bakreferansene som kommer i neste del.
Grupper og alternasjon
Pipetegnet betyr eller, og parenteser grupperer, så ankre og alternativer spiller sammen:
$ printf '%s\n' cat dog catdog hotdog fish > pets.txt
$ grep -E 'cat|dog' pets.txt
cat
dog
catdog
hotdog
$ grep -E '^(cat|dog)$' pets.txt
cat
dog
Fire linjer har en cat eller en dog et sted i seg. fish er unntaket.
Med gruppen må hele linjen være det ene dyret eller det andre, og de to
hybridnavnene blir stående utenfor.
Én merknad om portabilitet: eldre BRE-basert stoff skriver grupper og
alternasjon med baklengs skråstrek, som i \(cat\|dog\). Gruppene
\( \) med baklengs skråstrek og bakreferansene \1 til \9 er
standard BRE-skrivemåte. Pipetegnet med baklengs skråstrek, \|, er
unntaket: en GNU-utvidelse, for POSIX BRE ikke har noen
alternasjonsoperator i det hele tatt. Den portable vanen er grep -E.
Presisjonsflagg
grep har tre måter å stramme inn hva som teller som et treff. -w
krever et helt ord, -x hele linjen, og det finnes også en måte på
mønsternivå å markere ordgrenser på, \b:
$ grep -w 'cat' words.txt
cat
a cat category
$ grep -x 'cat' words.txt
cat
$ printf '%s\n' 'a cat category' category > wb.txt
$ grep '\bcat\b' wb.txt
a cat category
$ grep -o '\bcat\b' wb.txt
cat
category inneholder bokstavene c-a-t, men de er limt fast til andre
bokstaver, så testen for helt ord avviser den, -x dytter ut til og
med setningen, og \b stryker den på samme måte. Forbeholdet betyr
noe: \b er oppførsel fra GNU grep, og manualen merker at den er
uspesifisert utenfor GNU, så portable skript holder seg unna den.
grep -w dekker det samme terrenget på GNU grep, men POSIX
spesifiserer den ikke, og det samme gjelder -o fra delen om
stjernen, som fortsatt kombineres med et hvilket som helst mønster når
du bare vil se det som matchet.
Mønstre i sed
sed tar imot mønstre på to steder. Det første er adressen, delen foran
en kommando som velger hvilke linjer den skal kjøre på: /^ERROR/
velger linjer som matcher mønsteret, og p skriver dem ut, mens -n
demper seds vane med å gjenta hver linje. Det andre er substitusjon,
s/pattern/replacement/flags, der fangstgrupper kommer til sin rett
når rekkefølgen skal stokkes om: pakk deler av mønsteret inn i
parenteser, og referer til dem i erstatningen som \1, \2 og så
videre:
$ printf '%s\n' 'ERROR: disk full' 'WARNING: low memory' 'ERROR: timeout' 'info: ok' > log.txt
$ sed -n '/^ERROR/p' log.txt
ERROR: disk full
ERROR: timeout
$ sed -E 's/o+/0/g' oos.txt
0
0
0
b0k
r0t
$ echo 'item-42 and cog-7' | sed -E 's/([a-z]+)-([0-9]+)/\2-\1/'
42-item and cog-7
Adressen er samme ^ERROR som i grep-eksempelet. I substitusjonen ble
hver rekke med o-er til én enkelt 0, med -E i samme betydning som for
grep. -r er en eldre skrivemåte for samme flagg, og manualen
anbefaler -E av portabilitetshensyn. Flagget g utvider byttet til
hver forekomst i linjen: utelater du g, blir bare den første
forekomsten per linje erstattet. Byttet fanget opp bokstaver og sifre
rundt en bindestrek, og \2-\1 byttet plass på dem. Se på det andre
feltet: cog-7 kom uskadd gjennom, regelen uten g i aksjon. Alt som
mønsteret ikke matchet, går uendret gjennom.
Anførselstegn: skallet får mønsteret ditt først
Et regex når grep først etter at bash har tatt sin runde, og bash
omskriver flere regex-tegn til sine egne formål. For å se det skje,
lager du en liten logg og én sabotør, en tom fil med navnet cat:
$ printf '%s\n' 'cut here' 'c-t here' 'Error: one' 'plain cat word' > f.log
$ touch cat
$ grep 'c[au]t' f.log
cut here
plain cat word
$ grep c[au]t f.log
plain cat word
$ grep Error: one f.log
grep: one: No such file or directory
f.log:Error: one
$ grep (cat|word) f.log
bash: -c: line 1: syntax error near unexpected token 'cat'
bash: -c: line 1: 'grep (cat|word) f.log'
$ grep -E '(cat|word)' f.log
plain cat word
Versjonen med anførselstegn er klassen som gjør jobben sin: cut
matcher, c-t gjør ikke det, og det vanlige ordet cat matcher også.
Versjonen uten anførselstegn kom tilbake med én linje i stedet for to.
bash så c[au]t, kjente igjen en glob, fant filen med navnet cat i
denne mappen og erstattet hele ordet med det filnavnet, så grep søkte
etter strengen cat. Linjen cut here forsvant, kommandoen avsluttet
med exit status 0, og ingenting antyder at mønsteret du skrev ikke er
mønsteret grep kjørte. grep så aldri hakeparentesene dine. bash hadde
alt spist dem og overlevert et filnavn.
Mellomromsfeilen tilstår i det minste: linjen ble delt i mønsteret
Error: pluss to filnavn, og grep lette i one, som ikke finnes.
Parentesene er shell-syntaks, så bash avviste den linjen før noe søk
hadde begynt, mens versjonen med anførselstegn og -E virket. Skal
bash dømmes rettferdig: ^ er ikke noe spesialtegn for det, og et bart
$ foran et mellomrom forblir bokstavelig. Det er derfor
grep '^ERROR' date.log tilfeldigvis virker uten anførselstegn. Men
hakeparenteser, mellomrom og parenteser forvanskes alle sammen, og det
finnes ingen premie for å huske hvilke som er trygge. Sett
enkelanførselstegn rundt mønsteret hver gang, så gir skallet det videre
urørt.
Avslutningsprosjekt — grave i en liten logg
Dataloggen fra ankerdelen pluss to små filer er nok til å ta hele verktøykassa i bruk, i rekkefølge. Først: hent ut linjene som bærer en dato, fire sifre, bindestrek, to sifre, bindestrek, to sifre, og til slutt et mellomrom. Tell så skaden, se bort fra store og små bokstaver, og form et felt om:
$ grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2} ' date.log
2026-09-21 ERROR: disk full
2026-09-20 INFO: backup done
2026-09-19 INFO: cleanup ok
$ grep -c 'ERROR' log.txt
2
$ printf '%s\n' 'ERROR: disk full' 'error: minor' 'Error: mid' > case.txt
$ grep -i 'error' case.txt
ERROR: disk full
error: minor
Error: mid
$ echo 'item-42' | sed -E 's/([a-z]+)-([0-9]+)/\2-\1/'
42-item
Les datomønsteret én ingrediens om gangen: ^ foranker mønsteret til
linjestarten, [0-9] er sifferklassen, {4} og de to {2} er
ERE-intervaller som krever nøyaktige antall, og det avsluttende
mellomrommet holder mønsteret ærlig på hva som følger etter datoen. De
to linjene uten dato stryker på ankeret. -c telte matchende linjer,
og -i så bort fra store og små bokstaver, slik at de tre skrivemåtene
av samme nivå faller sammen til én liste.
Hvor du står nå
Et mønster er tekst som beskriver tekst, men delene hoper seg raskt
opp: bokstavelige tegn, punktum, ankre, klasser, stjernen, og så
ERE-settet av +, ?, intervaller, grupper og alternasjon, med sed
som setter samme grammatikk til arbeid på adresser og substitusjon. Du
kan lese et mønster som ^[0-9]{4}-[0-9]{2}-[0-9]{2} del for del,
sette anførselstegn rundt det slik at bash ikke kan omskrive det, og
si hvilken dialekt en kommando snakker.
Herfra er kapittelet Tekstbehandling fortsatt referansen for verktøyene selv, og oppskriftene der leses annerledes nå som mønsterhalvdelen ikke lenger er ugjennomtrengelig. For hele den formelle historien er tre referanser verdt å ha åpne, alle på engelsk:
- GNU grep-manualen — mønstesyntaks og hvert flagg som er brukt her
- GNU sed-manualen — adresser, s-kommandoen og resten
- regex(7)-mansiden — POSIX-grammatikken bak begge dialektene