På denne siden

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: