Jorin jutut / Unix

grep -komento ja säännölliset lausekkeet

Grep -komennolla tulostetaan tiedostosta rivit, joista löytyy jokin hahmo. "Hahmo" on yksinkertaisimmillaan pelkkä kirjainjono, mutta se voi sisältää myös tietyllä tavalla muodostettuja monimutkaisempia rakenteita. Esimerkiksi grep 'kissa' teksti.txt tulostaa tiedostosta teksti.txt ne rivit joissa esiintyy merkkijono "kissa", ja grep 'kis\+a' teksti.txt tulostaa rivit, joissa esiintyy "kisa", "kissa", "kisssa" jne.

Grep on hyvin monipuolinen ohjelma. Sillä voidaan hakea vaikka rivejä joissa on peräkkäin 'x', 'y', neljästä kuuteen vokaalia ja 'z', tai rivejä joissa on sana "kissa" erotuksena merkkijonoon "kissa" (vrt. sana "takissani") tai joissa "kissa" on rivin alussa tai lopussa. Grepillä voi hakea yhdellä kertaa useammasta tiedostosta, ja tarvittaessa näyttää vain nimet niistä tiedostoista joissa hakusana esiintyy. Voidaan myös vain laskea monellako rivillä haettava merkkijono on, tai hakea ne rivit joissa haettavaa merkkijonoa ei ole. Haettavat merkkijonotkin voidaan lukea tiedostosta. Voidaan myös laskea monellako rivillä on vain sana kissa eikä mitään muuta. Tarvittaessa voidaan tulostaa haluttu määrä rivejä ennen ja jälkeen osumakohdan.

Tämän sivun on tarkoitus kertoa kaikki grep-komennon käytöstä. Sivulla on ensin taulukko optioista ja sitten selitystä optioista, jotka ensimmäisellä lukukerralla kannattaa silmäillä hyvin lyhyesti läpi ja kokeilla jotakin itse. Kokeilun jälkeen on parasta palata optioihin uudelleen ja kokeilla niitä järjestyksessä. Tämän jälkeen seuraa säännölliset lausekkeet, eli kerrotaan miten hahmoja muodostetaan.

Sivulla esitellään GNU-versiota grep-komennosta. Tämä grep-versio tulee tavallisten Linux-jakelupakettien mukana (Esim. Fedora ja Debian), ja voidaan asentaa muihinkin Unix-tyyppisiin järjestelmiin. Muut grepin versiot ovat yleensä suppeampia kuin tässä esiteltävä GNU-grep.

Grep-komennon optiot

Kuten muillakin GNU-komennoilla, on myös grepillä useimmista optioista pitkät ja lyhyet muodot. Ne toimivat täsmälleen samalla tavalla. Kirjoittamisen vaivaa säästää käyttää lyhyitä muotoja, mutta toisaalta monimutkainen komento voi olla muillekin ymmärrettävä jos on käytetty pidempiä muotoja.

OptioPitkä muotoMerkitys
Ohjeet ja versionumero
-V--versionTulosta grep-komennon versionumero.
--helpTulosta grep-komennon ohje.
Osumakohdan konteksti
-B N--before-context=NTulosta N riviä ennen osumakohtaa.
-A N--after-context=NTulosta N riviä osumakohdan jälkeen.
-C N--context=NTulosta N riviä ennen ja jälkeen osumakohdan. Myös pelkkä -N, jossa N jokin luku, tekee saman asian.
Osumakohdan sijainti
-n--line-numberTulosta rivinumerot ennen osumarivejä.
-b--byte-offsetTulosta ennen löydettyä riviä monennestako tiedoston merkistä rivi alkaa.
Binäärimössön käsittely
--binary-files=TYYPPIKertoo mitä tehdään jos tiedosto näyttää olevan binäärimössöä eikä tekstiä. (Oletus: tulosta "binary file XX matches".)
-a--textÄlä tarkista onko kyse tekstitiedostosta: tulosta aina kahden rivinvaihtomerkin välinen osa, kun osuma löytyy. (Lyhempi tapa sanoa --binary-files=text.)
-IJätä huomiotta binääritiedostot. (Lyhyempi tapa sanoa --binary-files=without-match.)
Haku alihakemistoista
-d MITATEHDA--directories=MITATEHDAKertoo mitä hakemistoille tehdään. (Oletus: "read", joka on käytännössä sama kuin "skip".)
-r
-R
--recurseKäy läpi kaikki alihakemistojen tiedostot. (Lyhyempi tapa sanoa --directories=recurse.)
Haun rajoittaminen tiedoston nimen perusteella
--include=NIMIMALLIKäy läpi vain ne tiedostot, joiden nimi sopii nimimalliin NIMIMALLI.
--exclude=NIMIMALLIKäy läpi vain ne tiedostot, joiden nimi ei sovi nimimalliin NIMIMALLI.
--exclude-from=TIEDOSTOKäy läpi vain ne alihakemistot, joiden nimi ei sovi mihinkään niistä nimimalleista jotka ovat tiedostossa TIEDOSTO.
Hahmon käsittely
-v--invert-matchKäänteinen haku, tulosta ne rivit joista hahmoa ei löydy.
-m N--max-count=NLopeta, kun N osumariviä on löydetty. Yhdessä --count -option kanssa tulostaa enintään luvun N. Yhdessä --invert-match -option kanssa tulostaa enintään N riviä joissa osumaa ei löydy.
-w--word-regexpHae vain rivit, joissa hahmo esiintyy yhtenä sanana, ei sanan osana.
-x--line-regexpHae vain rivit, jotka täsmäävät täysin hahmoon, ei rivejä joissa hahmo esiintyy osana.
-i
-y
--ignore-casePidä isoja ja pieniä kirjaimia samana.
-F--fixed-stringsHahmo käsitellään kiinteänä merkkijonona, ts. hahmon kaikki erikoismerkit menettävät merkityksensä.
-G--basic-regexpKäsitellään haettava hahmo ns. perussyntaksin mukaisesti.
-E--extended-regexpKäsitellään haettava hahmo laajennetun syntaksin mukaisesti.
-P--perl-regexpKäsitellään hahmo ilmaisuvoimaisempana perl-tyylin säännöllisenä lausekkeena.
Muuta osuman tulostusta
-q--quiet
--silent
Älä tulosta mitään, palauta vain paluuarvo sen mukaan löytyikö osuma vai ei.
-l--files-with-matchesÄlä tulosta löytyneitä rivejä, vaan niiden tiedostojen nimet joista hahmo löytyi.
-L--files-without-matchÄlä tulosta löytyneitä rivejä, vaan niiden tiedostojen nimet joista ei löytynyt haettavaa hahmoa.
-c--countÄlä tulosta löytyneitä rivejä, vaan niiden lukumäärä
-o--only-matchingÄlä tulosta koko riviä vaan vain se osa, joka täsmää hahmoon.
-H--with-filenameTulosta aina sen tiedoston nimi, josta osuma löytyi. (Oletuksena tulostetaan, jos haetaan useammasta kuin yhdestä tiedostosta kerralla.)
-h--no-filenameÄlä koskaan tulosta niiden tiedostojen nimeä, joista osuma löytyy. (Oletuksena tulostetaan, jos haetaan useammasta kuin yhdestä tiedostosta kerralla.)
--color=VARITETAANKO
--colour=VARITETAANKO
Määrittelee väritetäänkö osumakohta aina (always), ei koskaan (never) vai silloin kun tulostus on ohjattu näytölle (auto).
DOS-rivinvaihtoihin liittyvää
-U--binaryÄlä muuta rivinvaihtoja Unix-tyylisiksi ennen hakua, vaikka tiedosto näyttäisi sisältävän DOS-tyyppisiä rivinvaihtoja.
-u--unix-byte-offsetsNäytä --byte-offset -option tuloste niin, että ensin muutetaan rivinvaihdot DOS-tyylisistä Unix-tyylisiksi.
Teknistä sälää
--line-bufferedTulosta aina rivi kerrallaan, älä puskuroi normaalisti. (Oletuksena puskuroidaan, jos syöte ohjautuu muualle kuin näytölle.)
--mmapKäytä mmap-systeemikutsua eli mappaa tiedosto muistiin. Joskus nopeampi, mutta voi sekoilla tai kaatuakin jos tiedostoa muutetaan kesken greppauksen.
Sekalaiset
-f TIEDOSTO--file=TIEDOSTOHae sovitettavat hahmot tiedostosta.
-s--no-messagesÄlä tulosta mitään virheilmoituksia puuttuvista tiedostoista tai lukuoikeuksien puuttumisesta.
-D MITATEHDA--devices=MITATEHDAKertoo mitä tehdään jos tiedosto ei olekaan tavanomainen tiedosto vaan laitetiedosto, nimetty putki tai soketti.
-e HAKULAUSEKE--regexp==HAKULAUSEKEMäärittelee haettavan hahmon. Tarvitaan, jos haettava hahmo alkaa viivalla, tai jos haetaan kerralla useampaa hahmoa.
--label=NIMINäytä kantasyötevirrasta tuleva syöte kuten se tulisi tiedostosta nimeltä NIMI.
-Z--nullTulosta tiedostonimen jälkeen nollatavu, eikä kaksoispistettä (kuten normaalisti) eikä rivinvaihtoa (kuten --files-with-matches ja --files-without-matches -optioiden kanssa normaalisti).

Selitystä optioista

Pikaohje ja versionumero

Melkein kaikissa GNU-työkaluissa on optiot --help ja --version. Jos kysyt miksi grep ei toimi, liitä mukaan komennon grep --version tuloste, se helpottaa vastaamista. Ennen kysymystä olet tietysti tarkistanut josko grep --help auttaisi.

Konteksti ja osumakohdan sijainti

Optioiden --after-context (-A), --before-context (-B), --context (-C) ja --line-number (-n) toiminta lienee itsestäänselvää. --byte-offset (-b) sen sijaan toimii hieman yllättävästi: grep 'x' tiedosto.txt ei kerro monesko merkki 'x' on tiedostossa, vaan monesko merkki aloittaa sen rivin, jossa 'x' esiintyy. Lisäksi täytyy huomata, että rivien numerointi alkaa yhdestä, mutta merkkien numerointi alkaa nollasta.

Rekursiivinen haku alihakemistoista

Ennen wanhaan unixin hakemisto oli sisäisesti likimain tekstitiedosto, niin että grep 'abc' hakemisto teki suunnilleen saman asian kuin ls hakemisto | grep 'abc' . Muistona tästä voi grepille antaa optiot --directories=read ja --directories=skip, joista ensinmainittu on oletusarvo ja viimeksimainittu ohittaa hakemistot. Koska Linuxissa (ja uudemmissa Unixeissa muutenkin) hakemisto on toteutettu teknisesti toisin, toimii --directories=read todellisuudessa kuten --directories=skip.

Mielenkiintoisempi on --directories==recurse eli lyhyemmin --recursive (-r). Se käsittelee hakemistot rekursiivisesti, eli käytännössä grep --recursive 'kissa' * etsii "kissa"-merkkijonoa kaikista työhakemiston alla olevista tiedostoista. Tämä on joskus kätevää, mutta usein joutuu käyttämään find-xargs -komentoparia monimutkaisempien hakujen toteuttamiseen. Huomaa, että esimerkiksi grep --recursive 'class' *cpp ei luultavasti tee mitään: se etsii rekursiivisesti merkkijonoa "class" kaikista niistä työhakemiston alihakemistoista, joiden nimi päättyy "cpp".

Haun rajoittaminen tiedoston nimen perusteella

Lähinnä rekursiivisen haun kanssa käyttökelpoisia ovat --include ja --exclude. Ensinmainittu käsittee vain ne tiedostot, joiden nimeen annettu nimimalli sopii, ja toinen jättää näistä pois ne, joiden nimeen sen hahmo sopii. "Nimimalli" on tässä suunnilleen shellin käyttämä tapa kuvata tiedostonimiä, ei grepin säännöllisten lausekkeiden mukainen kuvaus. Toiminta selviää parhaiten esimerkistä:
grep --recursive --include='*.h' --exclude='_*' class source
tutkii hakemiston "source" alla olevat tiedostot, joiden pääte on ".h" ja jotka eivät ala alaviivalla, ja etsii niistä rivit joissa on sana "class".

Edellämainittuja optioita voi käyttää moneen kertaan. Jos usein joutuu kirjoittamaan monta samaa --exclude -optiota, voi käyttää --exclude-from=TIEDOSTO -optiota. Silloin jätetään tutkimatta ne tiedostot, joiden nimi täsmää tiedoston TIEDOSTO jollain rivillä olevaan nimimalliin.

Hahmon käsittely

--invert-match (-v) ja --line-regexp (-x) lienevät itsestäänselviä: ensimmäinen tulostaa ne rivit, joihin hahmo ei sovi, ja toinen vain rivit joihin hahmo sopii kokonaisuudessaan. Yhdessä ne tulostavat rivit, jotka kokonaisuudessaan eivät osu haettavaan hahmoon.

--word-regexp (-w) on periaatteessa selvä: haetaan vain kokonaista sanaa, johon hahmo sopii. Mikä sitten on sana? Sana koostuu yhdestä tai useammasta peräkkäisestä sanamerkistä, jota edeltää rivin alku tai muu kuin sanamerkki, ja jonka jälkeen on rivin loppu tai muu kuin sanamerkki. Sanamerkki taas on kirjain, numero tai alaviiva. Mikä sitten on kirjain, ja ovatko ää ja öö kirjaimia? Se riippuu locale-asetuksesta, katso tarkemmin kohdasta mikä on kirjain.

Myös --ignore-case (-i) on periaatteessa selvä: pidetään isoja ja pieniä kirjaimia samanarvoisena. Mistä kone sitten tietää, että 'a' ja 'A' ovat saman kirjaimen eri kirjasinkokoja? Sekin riippuu localesta, ks. kohta mikä on kirjain.

--max-count (-m) on myös ymmärrettävä: haetaan korkeintaan tietty määrä osumarivejä. Myös yhdessä --only-matching -option kanssa se käsittelee tietys määrän rivejä, ei osumia:
echo "kissassa" | grep --max-count=1 --only-matching ss
tulostaa kahteen kertaan "ss". --max-count jättää tiedosto-osoittimen juuri osumarivin jälkeen, minkä vuoksi voit esimerkiksi etsiä ensimmäisen rivin jossa on merkkijono "kissa" ja sen jälkeen tulevat rivit joissa on merkkijono "koira":
(grep --max-count=1 'kissa' ; grep 'koira') < teksti.txt
(Tuon tarkempi ymmärtäminen edellyttää komentotulkin toiminnan tuntemusta.)

--fixed-strings (-F) käsittelee hahmon vain ja ainoastaan peräkkäisinä merkkeinä. Tämä on usein kätevää, jos haettava merkkijono on kiinteä, ja sisältää pisteen tai muita grepin käyttämiä erikoismerkkejä.

--basic-regexp (-G) ja --extended-regexp (-E) määrittävät käytetäänkö hahmojen esittämiseen perussyntaksia vai laajennettua syntaksia. --perl-regexp (-P) ottaa käyttöön hieman laajemman valikoiman keinoja koota säännöllisiä lausekkeita hahmoihin. Ne on selitetty alla kohdassa säännölliset lauseet.

Tulostuksen muuttaminen

Kaikkein yksinkertaisin tapa muuttaa tulostusta on --quiet (-q), joka poistaa kaiken tulostuksen. Ideana on hyödyntää paluuarvoa: grep palauttaa nollan jos yksikin osuma löytyi, ykkösen muuten (ja kakkosen, jos tuli ongelmia). Tätä voi käyttää vaikka näin:
grep --quiet kissa teksti.txt && echo 'Kissa löytyi' || echo 'Ei kissaa'

Oletusarvoisesti grep tulostaa tiedoston nimen ennen osumaa, jos haetaan kerralla useammasta kuin yhdestä tiedostosta. Jos nimiä ei tarvita, voi käyttää optiota --no-filename (-h), ja jos päinvastoin halutaan aina tiedostonnimi mukaan, oikea optio on --with-filename (-H).

Jos halutaan tulostaa osumarivien määrä eikä itse rivejä, voi käyttää --count (-c) -optiota. Se tulostaa nimenomaan rivien määrän, vaikka jollakin rivillä olisi useampi osuma: kokeile esimerkiksi
echo "hei hei" | grep --count hei

--files-with-matches (-l) ja --files-without-match (-L) tekevät mitä voisi olettaa: tulostavat vain niiden tiedostojen nimet, joista löytyy yksikin osuma tai ei yhtäkään osumaa.

--only-matching (-o) tulostaa vain osuman, ei osuman sisältävää riviä. Tästä on iloa, jos käyttettävä hahmo on monimutkaisempi kuin pelkkä merkkijono. Jos osumia löytyy useampia, tulostetaan jokainen omalle rivilleen. Esimerkki:
echo "abc acb adb" | grep -o -w "a.b"

Normaalisti grep ei tulosta osumaa värillisenä, ja jos tämän haluaa varmistaa voi antaa option --color=never. --color=always tulostaa osumakohdan värillisenä, mutta yleensä halutaan värillinen tulostus vain näytölle, ei silloin kun tulostus on ohjattu tiedostoon. Tämä onnistuu sanomalla --color=auto, tai lyhyemmin pelkästään --color . Tätä voit testata näillä viidellä komennolla:
echo takissani | grep --color=auto kissa
echo takissani | grep --color=always kissa > t1
echo takissani | grep --color=auto kissa > t2
cat t1
cat t2

Värien näyttäminen perustuu ANSI-koodeihin, eli tiettyihin merkkeihin jotka saavat konsolin esimerkiksi vaihtamaan tulostusväriä. Värit toimivat joillakin SSH-ohjelmilla ja toisilla eivät, ja saattavat joskus toimia väärinkin. Voit kokeilla omaa ohjelmaasi komennolla
echo "abc"$'\x1b'"[44mdef"$'\x1b'"[0mghi"
joka tulostaa kirjaimet def sinisellä pohjalla. --color -optio saa ennen osumakohtaa tulostumaan GREP_COLOR-ympäristömuuttujan arvon ja m-kirjaimen, ja osuman jälkeen ESC-merkin ja merkit "[0m". ESC-hakasulkuauki-nolla-m on merkintä joka palauttaa tulostuksen normaaliksi, ja jos GREP_COLOR -ympäristömuuttujan arvo on vaikkapa ESC ja "[44", on tuloksena sininen osumakohta. Tätä voi kokeilla komennoilla
export GREP_COLOR=$'\x1b'"[44"
echo abcdefghi | grep --color=auto def

Sekalaisia optioita

--regexp (-e) -optiolla annetaan haettava hahmo. grep a b c tulkitaan niin, että tiedostoista b ja c haetaan hahmoa a. Komento grep -e a -e b c taas hakee sekä hahmoa a että hahmoa b tiedostosta c. Valitettavasti grepissä (versio 2.5.1) on bugi tässä, joten
echo abc | grep --color -e a -e c
toimii mutta
echo abc | grep --color -e c -e a
ei toimi.

--mmap -optio ohjaa grepin käyttämään toista, joskus nopeampaa mutta ei aivan aina toimivaa, tapaa tiedoston lukemiseen. Älä käytä.

--no-messages -optio poistaa valitukset tiedostoista, joita ei saa avattua esimerkiksi puuttuvien oikeuksien vuoksi. Tämä on käyttökelpoinen lähinnä kun tiedostot annetaan jokerimerkeillä, ja mukaan tulee muita kuin omia tiedostoja.

--file (-f) -optio ja mitä sillä voi tehdä

grep --file=hahmot.dat juttu.txt
tulostaa tiedostosta juttu.txt ne rivit, jotka täsmäävät johonkin tiedoston hahmot.dat rivillä olevaan hahmoon. Tätä tarvitaan ainakin silloin, kun halutaan selvittää mitkä kahden tiedoston rivit löytyvät molemmista tiedostoista ja mitkä vain toisesta. Komennot
grep --line-regexp --fixed-strings --file=eka toka
grep --line-regexp --fixed-strings --invert-match --file=eka toka
grep --line-regexp --fixed-strings --invert-match --file=toka eka
tulostavat molemmissa tiedostoissa olevat rivit, vain tiedostossa toka ja vain tiedostossa eka olevat rivit.

Sekä grep-komennon argumenttina oleva tiedoston nimi että --file -option arvo voi olla pelkkä viiva, jolloin syöte luetaan kantasyötevirrasta. Tämän ansiosta voidaan tutkia vaikka ketkä tietystä käyttäjäjoukosta, jonka käyttäjätunnukset on listattu tiedoston kayttajat.txt riveillä, ovat parhaillaan kirjautuneena koneelle näin:
w -h | cut -f 1 -d ' ' | grep --file=kayttajat.txt -
tai ketkä eivät ole:
w -h | cut -f 1 -d ' ' | grep --invert-match --file=- kayttajat.txt

Binäärimössöstä

Grep arvaa tiedoston alun perusteella onko kyseessä tekstitiedosto vai ei-tekstiedosto. Jälkimmäisessä tapauksessa tulostetaan vain "Binary file XX matches" jos osuma löytyy. Tämä oletusarvo on järkevä, mutta joskus halutaan ohittaa binääritiedostot sanomalla --binary-files=without-match (sama lyhyesti: -I). Optiolla --binary-files=text (sama kuin --text tai -a) on käyttöä, jos tiedät tiedoston alun olevan binäärimössöä mutta lopusta löytyvän tekstiä. Esimerkiksi Microsoft Word -ohjelman .doc -tiedostot ovat yleensä tällaisia.

Laitetiedostot ja --devices

Unixissa "kaikki on tiedostoja", joten esimerkiksi normaalin korpun saa kopioitua antamalla ensin komennon cp /dev/fd0 /joku/polku/tiedosto ja sitten levynvaihdon jälkeen cp /joku/polku/tiedosto /dev/fd0 . Normaalisti grep ei mitenkään erityisesti selvitä onko tutkittava tiedosto "normaali" vai laitetiedosto (tai jokin muu unix-erikoisuus, kuten nimetty putki). Jos haluaa, voi antaa option --devices=skip (-D skip), jolloin tällaiset erikoiset tiedostot ohitetaan.

Rivinvaihdot DOSissa

Unix-järjestelmissä, myös Linuxissa (ja mm. Amigoissa) on perinteisesti käytetty ascii -koodia 10 rivinvaihdon merkkinä, kun taas Macintoshien (ja mm. Commodore 64:n) valinta samaan tarkoitukseen on ollut numero 13. DOS- ja Windows-käyttöjärjestelmissä puhdas teksti taas ei sisällä rivinvaihtoja, vaan rivi-alas ja rivin-alkuun -merkit, joista ensinmainitun koodi on 10 ja viimeksimainitun 13. On kenties mahdollista, että asia olisi saatu vielä jollain tavalla sekavammaksi, mutta ainakaan tämän kirjoittajalle ei sellaista tapaa tule mieleen.

Jos grep ajetaan DOS- tai Windows-käyttöjärjestelmässä, se arvaa ensin tiedoston alun perusteella onko tutkittava tiedosto tekstimuotoista, ja jos on, niin muuttaa rivinvaihdot unix-muotoon ennen varsinaista käsittelyä. --binary (-U) poistaa tämän arvauksen käytöstä. Optiolla ei ole mitään vaikutusta, jos sitä käytetään Linuxissa tai muussa unix-tyyppisessä järjestelmässä.

Jos tiedostossa on merkit 'a', 'b', 'c', rivinvaihto ja 'x', on x-kirjain unixissa neljäs merkki ja DOSissa tai Windowsissa viides merkki. --unix-byte-offsets (-u) -optio muuttaa --byte-offset -option toimintaa niin, että osumakohta tulostetaan kuten rivinvaihdot olisivat olleet unix-tyyppisiä. --unix-byte-offset -optiolla on vaikutusta vain DOS- tai Windows-järjestelmissä.

Asiasta kiinnostuneet voivat lukea Jukka Korpelan kirjoituksen Rivinvaihdot ja kappaleet datan käsittelyssä.

Mitä --line-buffered tekee?

On nopeampaa kirjoittaa tiedostoon iso lohko, esimerkiksi 16 kilotavua, kerrallaan. Grep selvittää parhaansa mukaan onko tulostus menossa näytölle vai ohjattu jonnekin, ja sen mukaan päättää puskuroiko tulostusta rivi vai isompi lohko kerrallaan. Eräissä erikoisissa tilanteissa tämä arvaus menee pieleen.

Komennolla tail -f /var/log/messages näkee ylläpitäjä tiedoston /var/log/messages lopun reaaliajassa, ts. kun lokiin kirjoittuu uusi merkintä, tulee se heti näkyviin. Jos tästä halutaan poimia kiinnostavia rivejä, voidaan sanoa vaikkapa
tail -f /var/log/messages | grep 'FAILED LOGIN'
, mikä vielä toimii. Mutta jos otetaan toinen grep perään, esimerkiksi tutkitaan vain Villen epäonnistuneita logineja sanomalla
tail -f /var/log/messages | grep 'FAILED LOGIN' | grep ville
ei homma enää toimi. Tähän auttaa --line-buffered, pitää siis sanoa
tail -f /var/log/messages | grep --line-buffered 'FAILED LOGIN' | grep ville

Voit kokeilla tätä kahden konsolin avulla. Tee ensin komennolla touch esimerkki tyhjä tiedosto. Kirjoita sitten
tail -f jokutiedosto | grep a | tr 'a-z' 'A-Z' ja toisessa konsolissa sano echo kissa >> jokutiedosto. Nyt ensimmäisessä konsolissa ei tapahdu mitään. Sen sijaan lisäämällä --line-buffered -option saat tulostuksen heti.

--null: erottimeksi nollatavu eikä rivinvaihto

Tiedostojen nimissä voi olla myös välilyöntejä ja rivinvaihtoja. Tällöin esimerkiksi
grep --files-with-matches 'moi' *
voi tulostaa "kirje liisalle.txt", ja komento
grep --files-with-matches 'moi' * | xargs cat
yrittää tulostaa ensin tiedoston "kirje" ja sitten tiedoston "liisalle.txt". Ongelma korjautuu käskemällä ensiksi grep erottamaan tiedostonnimet nollatavulla käyttäen --null (-z) -optiota, ja toiseksi laittamalla xargs käyttämään nollatavua:
grep --files-with-matches --null 'moi' * | xargs -0 cat

--label - täh???

--label -option merkitys voi aiheuttaa hämmennystä henkilössä. Miksi kukaan sanoisi "cat tiedosto | grep --label=tiedosto 'kissa'" kun riittää sanoa "grep --with-filenames 'kissa' tiedosto"? Arvoituksen ratkaisu on komentojonoissa: yksinkertaisin esimerkki on komentojono (olkoon sen nimi vaikkapa zgrep) jonka ainoa rivi on
zcat $1 | grep --label=$1 $2
Tuota käytetään sanomalla "zgrep kissa tiedosto.gz", ja se toimii niin että zcat-komento purkaa pakatun tiedoston ja tulostaa sen, ja grep poimii tästä halutut rivit. Koska putkesta tuleva data pitää saada näyttämään siltä että se tulee tiedostosta, tarvitaan --label -optiota.

Oikeasti zgrep-niminen komentojono on jo tehty valmiiksi, ja se on paljon ylläolevaa monimutkaisempi.

Esimerkkejä optioiden käytöstä

grep --before-context=3 --after-context=1 'kissa' teksti.txt
etsii tiedostosta teksti.txt rivit joissa on merkkijono "kissa", ja tulostaa löytyneitä rivejä edeltävät kolme riviä sekä yhden seuraavan rivin.
grep -B 3 -A 1 'kissa' teksti.txt
tekee saman asian lyhyitä optioita käyttäen.
(grep --before-context=1 --max-count=1 'kissa' ; cat) < teksti.txt
tulostaa tiedoston alkaen merkkijonon "kissa" sisältävää riviä edeltävältä riviltä loppuun saakka. (Ensin haetaan grepillä yksi osuma, sen jälkeen cat tulostaa tiedoston loppuun saakka.)

grep --word-regexp --line-number 'kana' teksti.txt
tulostaa rivit, joissa on sana "kana" (eikä esim. sanaa "lakana"), ja näyttää monenneltako riviltä sana löytyy.
grep --line-number '' teksti.txt
tulostaa koko tiedoston rivinumeroituna. (Tyhjä hahmo osuu mihin vain.)

grep --recurse --files-with-matches 'localhost' /etc
Etsii hakemiston /etc alta tiedostot, joissa on merkkijono "localhost".
grep --exclude='*~' --files-without-match 'a' tekstit/*
tutkii hakemiston "tekstit" kaikki tiedostot poislukien ~-merkkiin loppuvat, ja tulostaa nimet niistä tiedostoista joissa ei ole yhtäkään a-kirjainta.

grep --ignore-case --quiet 'kissa' teksti.txt && echo 'Kissa löytyi'
tulostaa "Kissa löytyi", jos tiedosto teksti.txt sisältää merkkijonon "kissa" kirjainkoosta riippumatta (siis "kissa", "KISSA", "KiSsA" jne. kelpaavat).

grep --only-matching 'a.a' teksti.txt
tulostaa jokaisen sellaisen kohdan, jossa kahden a-kirjaimen välissä on mikä tahansa muu merkki (se saa olla myös a-kirjain, välilyönti tms.).
echo 'karamelli' | grep --color 'a.a'
tulostaa sanan "karamelli" niin että "ara" on väritetty punaiseksi.

grep -e '--binary' -- --mmap
etsii tiedostosta nimeltä "--mmap" merkkijonoa "--binary".

Säännölliset lausekkeet

Mitä säännölliset lausekkeet ovat?

Säännölliset lausekkeet (engl. "regular expression", usein vain "regexp") ovat tapa kuvata joitakin mahdollisesti äärettömän pitkiä merkkijonoja. Esimerkiksi "ensin A-kirjain, sitten mielivaltainen määrä BCD-kirjainkolmikkoja ja EF-kirjainpareja sekaisin ja lopuksi G-kirjain" on eräs säännöllinen lauseke. Se kuvattaisiin grep-komennon laajennetulla syntaksilla näin:
A((BCD)|(EF))*G
Syntaksi on epäolennainen: tärkeämpää kuin muistaa mikä merkki tarkoittaa mitäkin, on ymmärtää mitä säännöllisillä lausekkeilla voi tehdä ja mitä ei.

Kaikkia mekaanisia sääntöjä ei voi ilmaista säännöllisillä lauseilla: esimerkiksi "Ensin A-kirjaimia ja sitten B-kirjaimia miten monta vain, mutta kumpaakin kirjainta yhtä monta" ei ole kuvattavissa säännöllisenä lauseena.

Säännöllisten lauseiden ilmaisuvoima on helpointa ymmärtää katsomalla äärellisiä automaatteja. Äärellisellä automaatilla on tiloja ja niiden välillä siirtymiä. Ainakin yksi tila on alkutila ja ainakin yksi tila (mahdollisesti sama kuin alkutila) on lopputila. Jokaista siirtymää vastaa yksi merkki, ja automaatin hyväksymä kieli on sellainen merkkijono, joka muodostaa siirtymäpolun alkutilasta lopputilaan.

kuva1
Automaatti hyväksyy vain merkkijonon "ABC"

kuva2
Automaatti hyväksyy kielen "A((BCD)|(EF))*G".

Toinen tapa ymmärtää säännölliset lausekkeet on verrata niitä laskutoimituksiin. Ala-asteen matematiikassa on lukuja ja niitä käsittelevät yhteen-, vähennys-, kerto- ja jako-operaattorit. Säännöllisessä lausekkeessa lukuja vastaavat atomit ja operaattoreilla voidaan esittää peräkkäisyys, toisto tai vaihtoehtoisuus. 1*2+3*4 on lauseke, jossa plus-operaattori yhdistää kaksi kerto-operaattorin atomeista tuottamaa tulosta; (A|B)(C|D) on lauseke jossa peräkkäisyys (jolle ei ole omaa operaattoria) yhdistää kaksi vaihtoehto-operaattorin atomeista kokoamaa lauseketta. Esimerkin säännöllinen lauseke tarkoittaa "ensin A tai B ja sitten C tai D".

grep, fgrep, egrep ja ilmaisuvoima

Yllä kerrottu --fixed-strings -optio tarkoittaa, että hahmo tulkitaan pelkäksi kiinteäksi merkkijonoksi. Helpompi tapa sanoa sama asia on käyttää komentoa fgrep. Eli kun grep 'a.b' tarkoittaa "tulosta rivit, joilla on a, mikä tahansa merkki (pisteen erikoismerkitys) ja b", tarkoittaa fgrep 'a.b' nimenomaan "tulosta rivit, joilla on a, piste ja b".

Joissakin grep-ohjelman versioissa on olemassa erikseen perushaku ja ilmaisuvoimaisempi laajennettu haku, joista viimeksimainittu otetaan käyttöön optiolla --extended-regexp tai käyttämällä komentoa egrep. GNU-grepin kanssa näin ei ole, vaan siinä egrep (tai vastaava optio) ottaa käyttöön laajennetun syntaksin. Näissä esimerkeissä käytetään nimenomaan laajennettua syntaksia, perussyntaksissa merkkien '?', '+', '{', '(', ')' ja '|' eteen pitäisi kirjoittaa '\'. (Esimerkki: rivit, joissa on joko merkkijono "kissa" tai "koira" etsitään joko komennolla grep 'kissa\|koira' tai egrep 'kissa|koira' tai grep --extended-regexp 'kissa|koira'.)

Merkkien '*' ja '.' merkitys on sama perussyntaksissa ja laajennetussa syntaksissa. Niinpä perussyntaksia käyttäen pitäisi muistaa kirjoittaa esimerkiksi grep 'ab*c' mutta toisaalta grep 'ab\+c'. Tämä on hankala muistaa. Käytännössä kannattaa aina käyttää joko fgrep -komentoa tai laajennettua syntaksia, eikä koskaan perussyntaksia.

Ilmaisuvoimaa voidaan laajentaa ottamalla käyttöön ns. perl-tyylin säännölliset lausekkeet optiolla --perl-regexp. (Komento nimeltä pgrep on olemassa, mutta se tarkoittaa aivan muuta; pcregrep taas ei tue kaikkia niitä optioita, joita grep ja egrep tukevat). Tästä kerrotaan lisää alempana.

Peruspalikat

Merkki, jolla ei ole erikoismerkitystä, tarkoittaa säännöllisessä lauseessa itseään; eli "kissa" on säännöllinen lauseke joka osuu vain peräkkäisiin merkkeihin 'k', 'i', 's', 's' ja 'a'. Jos halutaan erikoismerkki mukaan hakulausekkeeseen, sitä pitää edeltää kenoviiva. Kaksi kenoviivaa peräkkäin tarkoittaa yhtä kirjaimellista kenoviivaa.

Toisto ja valinta

Toisto-operaattoreita on selkeyden vuoksi seitsemän:
A*Nolla tai useampi A-kirjainta
A+Yksi tai useampi A-kirjainta
A?Nolla tai yksi A-kirjainta
A{N}N A-kirjainta
A{N,}N tai useampi A-kirjain
A{,M}Enintään M A-kirjainta
A{N,M}N-M A-kirjainta

Valinnan osalta riittää yksi merkki: "A|B" tarkoittaa joko A- tai B-kirjainta.

Sulkeilla ryhmitellään

Merkkejä voi niputtaa sulkeilla, ja tällöin perässä oleva toisto-operaattori viittaa sulkeiden sisältöön: "(AB)+" tarkoittaa merkkijonoja "AB", "ABAB", "ABABAB" jne. Sulkeita saa olla sisäkkäin, esimerkiksi "((AB){1,2}(CD){1,2}){1,2}" kuvaa merkkijonoja "ABCD", "ABABCD", "ABCDCD", "ABABCDCD", "ABCDABCD", "ABCDABABCD", "ABCDABCDCD", "ABCDABABCDCD" "ABABCDABCD", "ABABCDABABCD", "ABABCDABCDCD", "ABABCDABABCDCD", "ABCDCDABCD", "ABCDCDABABCD", "ABCDCDABCDCD", "ABCDCDABABCDCD", "ABABCDCDABCD", "ABABCDCDABABCD", "ABABCDCDABCDCD" ja "ABABCDCDABABCDCD". Hahmo "((AB|C)*(D|E)+)*" sopii esimerkiksi merkkijonoon "ABDEEDABECABCCABE". (Testikysymys: pitääkö hahmoon sopivan merkkijonon aina loppua D- tai E-kirjaimeen?)

Hakasulkumerkinnät

(A|B|C|D|E) on hankala kirjoittaa, ja se voidaan ilmaista lyhyemmin [ABCDE] tai vielä kätevämmin [A-E]. Jos haetaan merkkiä joka ei kuulu joukkoon, voidaan sanoa [^A-E]. Hakasulkumerkinnän tarkka merkitys riippuu käytettävistä kieliasetuksista, eli [A-Ö] voi tehdä mitä haluat tai sitten ei. Merkkivälejä hakasuluissa voidaan antaa enemmänkin, eli esim. [A-CK-N] tarkoittaa merkkejä A, B, C, K, L, M ja N.

Lopuksi on vielä joukko merkintöjä yleisille merkkiluokille.
[:lower:]pieni kirjain
[:upper:]iso kirjain
[:alpha:]kirjain, iso tai pieni
[:digit:]numero
[:alnum:]kirjain tai numero
[:blank:]välilyönti ja tabulaattori
[:graph:]"näkyvä merkki", eli kirjain, numero tai välimerkki
[:cntrl:]kontrollikoodi
[:print:]tulostettavissa oleva eli kirjain, numero, välimerkki tai välilyönti
[:punct:]"välimerkki", oikeastaan näkyvä merkki joka ei ole kirjain eikä numero
[:space:]"tyhjä merkki", eli mm. välilyönti tai form feed
[:xdigit:]heksadesimaalinumero, siis numero tai kirjain a-f isona tai pienenä

Hakasulut ovat osa merkintää, ja nämä toimivat ainoastaan hakasulkujen sisällä osana (mahdollisesti ainoana osana) vaihtoehtoisia merkkejä, mikä on lievästi sanottuna yllättävää. Esimerkiksi isoista kirjaimista koostuvat merkkijonot etsitään komennolla egrep --only-matching '[[:upper:]]+' ja vain isoja kirjaimia, numeroita ja pilkkuja sisältävät merkkijonot löytyvät näin: egrep --only-matching '[[:upper:][:digit:],]+'. Merkkijonot, joissa ei ole isoja kirjaimia, löytyvät komennolla egrep --only-matching '[^[:upper:]]+'.

Toisten hakasulkujen unohtaminen tekee muuta kuin voisi kuvitella: egrep '[:alpha:]' etsii ne rivit, joissa on jokin merkeistä kaksoispiste, 'a', 'l', 'p' tai 'h'. Eli älä unohda toisia hakasulkeita!

Rivin alku ja loppu; sanan alku ja loppu

Rivin alkua kuvataan hattumerkillä '^' ja rivin loppua dollarimerkillä '$'. Esimerkiksi grep -x kissa vastaa samaa kuin egrep '^kissa$'.

Sanan alku kuvataan merkinnällä '\<' ja sanan loppu vastaavasti merkinnällä '\>'. Esimerkiksi grep -w kissa tekee saman kuin egrep '\<kissa\>'.

\b osuu ikäänkuin nollan merkin pituiseen merkkijonoon sanan rajalla, ja \B taas kaikkeen muuhun paitsi sanan rajaan. \b voidaan aina korvata merkinällä \< tai \>, mutta \B -merkintää ei helposti esitä toisin: egrep 'a\B' etsii rivit joissa on a-kirjain, joka ei lopeta sanaa. (Mitä tekee egrep 'a[^\>]' ?)

Näitä ei suoraan voi kuvata äärellisellä automaatilla. Voi kuitenkin kuvitella, miten käsiteltävä teksti käydään ensin läpi ja merkitään mukaan rivien ja sanojen aloitus- ja lopetusmerkit.

Piste, \w ja \W

Pisteen erikoismerkitys on "mitä tahansa", joten esimerkiksi 'a..a' etsii rivit joissa on kaksi a-kirjainta joiden välissä on kaksi merkkiä. Tämä löytää myös rivit joissa on neljä peräkkäistä a-kirjainta. (Miksi?)

\w vastaa kirjaimia ja numeroita, \W taas vastaa kaikkia muita merkkejä; eli ne ovat synonyymeja merkinnöille [[:alnum:]] ja [^[:alnum:]].

Esimerkkejä

[Kuva vastaavasta automaatista]

Desimaaliluku

Ei-negatiivinen desimaaliluku on helppo esimerkki melko monimutkaisen lausekkeen koostamisesta pala kerrallaan. Oletetaan, että alku- ja loppunollia ei sallita, siis 1.23 on OK, mutta 01.23 ja 1.230 eivät ole. Tehdään ensin vaiheittain luonnollisen luvun tunnistava säännöllinen lauseke:

Sitten desimaaliosa:

Lopuksi pitää vain yhdistää osat, ja vielä muistaa että desimaaliosa kokonaisuudessaan ei ole pakollinen. Lopputulos näyttää näin kauniilta:
'(0|([1-9][0-9]*))(\.[0-9]*[1-9])?'.

Tuota voi testata helpoiten kirjoittamalla joitakin sallittuja ja joitakin ei-sallittuja rivejä tiedostoon, ja käyttämällä optiota --line-regexp grepille. Huomaa, että rivistä "0034" kyllä löytyy osumia, eli merkit "0", toinen "0" ja merkkipari "34" osuvat lausekkeeseen.

Merkkijono lainausmerkeillä erotettuna

Otetaan tehtäväksi etsiä merkkijono, joka on erotettu lainausmerkeillä. Vaikeutetaan tehtävää vielä sallimalla \" -merkintä merkkijonoon kuuluvan lainausmerkin merkkinä.

Ensimmäinen idea on yksinkertainen: '".*"'. Tuo hakee kaksi heittomerkkiä ja niiden välissä saa olla mitä tahansa merkkejä. Ei käy, sillä nyt 'Tässä on "eka" ja "toka" osuma' antaa tulokseksi '"eka" ja "toka"', koska "mitä tahansa" tarkoittaa myös heittomerkkiä. Pitää siis olla "mitä tahansa muuta paitsi heittomerkki": '"[^"]"'. Nyt toimii, ja osumat löytyvät.

Entä miten lisätään heittomerkin eskapointi? Yksinkertaista: '"(([^"])|(\\.))*"'. Siis alussa on heittomerkki ja lopussa myös, välissä taas on joko mitä tahansa merkkejä paitsi heittomerkkejä tai kenoviiva-mikätahansa -pareja. Esimerkki on helpompi ymmärtää kun katsoo viereisestä kuvasta vastaavaa äärellistä automaattia. Automaatilla on eräänlainen perustila, johon se pääsee heittomerkillä ja josta se samalla merkillä poistuu lopputilaan. Automaatti menee "seuraava on eskapoitu" -tilaan kenoviivan syödessään, ja siitä se palaa perustilaan millä tahansa merkillä.

HTML-elementin haku

Miten haetaan vastaavasti "<B>" ja "</B>" -tageilla erotettu teksti? Nyt automaatin pitää pysyä perustilassa aina kun se syö muun kuin <-merkin. <-merkin jälkeen joko palataan perustilaan, tai jos sattuu tulemaan vastaan kauttaviiva, niin siirrytään eteenpäin. Tästä edelleen palataan perustilaan, paitsi jos vastaan tuleekin B-merkki, ja seuraava tila tietenkin palauttaa perustilaan paitsi jos seuraava merkki on >-merkki. Hakulauseke on '<B>(([^<])|(<[^/])|(</[^B])|(</B[^>]))*</B>'. Tästä kannattaa piirtää automaatti, jotta ajatus selviää.

Laajennuksia säännöllisiin lausekkeisiin

Edellä kuvattuihin sännöllisiin lausekkeisiin on olemassa laajennuksia, jotka lisäävät niiden ilmaisuvoimaa. Näitä ei voi enää kuvata äärellisten automaattien avulla.

Viittaukset aiempiin osumiin

Sulkeilla on paitsi ryhmittelytehtävä, myös "kaappausominaisuus", jonka ansiosta niiden sisältöön voi myöhemmin viitata. Yksinkertaisimmillaan egrep '(.)\1' etsii kahta peräkkäistä merkkiä, ja egrep '(.)(.)\2\1' etsii neljän merkin "palindromeja".

Jokainen avaava sulku - siitä riippumatta onko sulkuja kenties sisäkkäin - aloittaa uuden kaappauksen, ja ne numeroidaan ykkösestä yhdeksään. Esimerkiksi
egrep '(([[:lower:]])\2\2) \1'
tulostaa rivit, joissa on kolme samaa pikkukirjainta, välilyönti ja samat pikkukirjaimet. Saman hahmon voisi kirjoittaa myös '([[:lower:]])\1\1 \1\1\1'.

Miten haetaan vaikkapa mielivaltainen HTML-elementti, jonka sisällä saa olla muita elementtejä? Näin onnistuu melkein:
egrep '<([^>]+)>.*</\1>'
Melkein siksi, että nyt taas tulee ongelmaksi kahden osuman yhdistyminen, kun merkkijono josta haetaan on mallia "a<TAGI>b</TAGI>c<TAGI>d</TAGI>e".

Edeltäjä- ja seuraajavaatimukset ja -kiellot

Tästä eteenpäin selitetään hahmoja, jotka vaativat --perl-regexp eli -P -option toimiakseen.

Seuraajavaatimus tarkoittaa, että hahmoa pitää seurata jokin toinen hahmo, mutta osumaksi valitaan vain ensimmäinen hahmo. A-kirjain, jota pitää seurata B-kirjain, haetaan hahmolla 'A(?=B)'. Tällä on merkitystä vain, kun osuma väritetään --color -optiolla tai tulostetaan vain osumakohta/kohdat --only-matching -optiolla; muussa tapauksessa voi yhtä hyvin hakea hahmoa 'AB'. Esimerkki selventänee:
echo 'ABBA' | grep --perl-regexp --only-matching 'A(?=B)'
echo 'ABBA' | grep --perl-regexp --only-matching 'AB'.

Seuraajavaatimus voi olla mielivaltainen hahmo, esimerkiksi '[[:digit:]]{3,5}(?=AB+C)' etsii 3-5 peräkkäistä numeroa, joita seuraa A, ainakin yksi B ja C.

Seuraajakielto toimii juuri päinvastoin: se etsii hahmon jota ei saa seurata jokin toinen hahmo. Esimerkiksi komennolla grep --perl-regexp 'A(?!B)' etsitään kaikki A-kirjaimet joita ei seuraa B-kirjain. Myös seuraajakiellossa hahmo saa olla mielivaltaisen monimutkainen. Mitä tekee grep --perl-regexp 'A(?!B(?!C))'?

Edeltäjävaatimus ja edeltäjäkielto toimivat muuten samalla tavoin mutta edeltävän hahmon on oltava määräpituinen. Hahmo '(?<=A)B' etsii B-kirjaimet joita edeltää A-kirjain, ja hahmo '(?<!A)B' etsii B-kirjaimet joita ei edellä A-kirjainta. Määräpituisuuden vaatimuksesta seuraa, että esimerkiksi '(?<=A{3}B{4})C' on sallittu hahmo, mutta '(?<=AB+C)D' ei ole.

Ahne ja kitsas haku

Säännöllinen lauseke sovitetaan yleensä kahta sääntöä noudattaen: ensiksi pyritään saamaan osuma alkamaan niin vasemmalta kuin mahdollista, ja toiseksi pyritään saamaan niin pitkä osuma kuin mahdollista, ts. ollaan ahneita ja yritetään saada syötettä sisään niin paljon kuin mahdollista. Tämän näkee helposti sanomalla
echo 'BAAAAAAB' | egrep --only-matching 'A{2,3}'
jolloin tuloksena on kaksi kolmen A-kirjaimen riviä. Haku voidaan muuttaa ahneesta kitsaaksi toisto-operaattorin perässä olevalla kysymysmerkillä:
echo 'BAAAAAAB' | grep --perl-regexp --only-matching 'A{2,3}?'
Nyt haetaan mahdollisimman vähän, eli käytännössä kaksi A-kirjainta kerrallaan.

Edellä huomattiin, että "<B>" ja "</B>" -tagien välisen tekstin haku oli hankalaa, ja mielivaltaisen elementin tunnistus mahdotonta, jos samalla rivillä oli useampia elementtejä. Tähän auttaa kitsas haku:
grep --perl-regexp --only-matching '<B>.*?</B>'
tulostaa nätisti molemmat B-elementit. Tätä voidaan vielä parantaa edeltäjä- ja seuraajavaatimuksilla:
grep --perl-regexp --only-matching '(?<=<B>)(.+?)(?=</B>)'
(Ei toimi, jos etsitään merkkijonosta "<B></B> <B>sana</B>". Miksi ei?)
Vastaavasti mielivaltaisen elementin tapauksessa toimii hahmo '<([^>]+)>.*?</\1>', joka on yksinkertaisempi kuin seuraajakiellon avulla toteutettu '<([^>]+)>(.(?!</\1>))*.</\1>'.

Muuntimia hahmon sisään

--ignore-case toimii ongelmitta, mutta joskus halutaan vain osa hahmosta tulkita kirjainkoko unohtaen. Tämä ei ole kovin vaikeaa:
grep --perl-regexp 'Velli(?i)kellon(?-i)soitto'
etsii rivit joissa on ensin merkkijono "Velli" täsmälleen noin eli isolla alkukirjaimella, sitten "kellon" kirjasinkoon ollessa merkityksetön, ja sitten "soitto" pienellä kirjoitettuna.

Yleisesti voidaan hakulausekkeen keskelle kirjoittaa muuntimia malliin '(?ab-cd)', jolloin ominaisuudet a ja b otetaan käyttöön ja siitä eteenpäin ja ominaisuudet c ja d taas poistetaan käytöstä.

Rivinvaihtojen käsittely - muuntimet m ja s

Normaalisti grep käsittelee aina yhtä riviä kerrallaan, ts. sillä ei voi esimerkiksi hakea merkkijonoa A-rivinvaihto-B, eikä etsiä tiedostoja joissa on ensin A-kirjain ja sen jälkeen mahdollisesti eri rivillä B-kirjain. Ensimmäisen ongelman ratkaisu on seuraava:
echo -e 'A\nB' | grep -P '(?s)A.B'

Muunnin s on lyhenne sanasta "single line", minkä nimityksen on ollut tarkoitus selittää, että tekstipätkä josta hahmoa haetaan tulkitaan yhdeksi riviksi. Käytännössä piste osuu tällöin myös rivinvaihtomerkkiin.

Toinen vastaava muunnin on m ("multiple line"), joka taas muuttaa ^- ja $-merkit osumaan vain koko tekstin alku- ja loppukohtaan yhden rivin alku- ja loppukohtien sijaan. Eli
grep --perl-regexp '(?m)(^A)|(B)' teksti.txt tulostaa jokaisen rivin, jossa on B-kirjain, sekä ensimmäisen rivin jos se alkaa A-kirjaimella. (Tai palauttaisi, jos toimisi. Tässä grep bugaa eikä toimi.)

Mikä on kirjain?

Tietokone käsittelee numeroita. "Hae merkkijonoa ABC" kääntyy koneessa muotoon "hae peräkkäisiä numeroita 65, 66 ja 67". Numeroiden ja kirjainten - tai oikeastaan merkkien - välinen yhteys on tietenkin mielivaltainen, mutta kaikkien pitäisi olla samaa mieltä siitä mitä numerokoodausta käytetään. (Lisätietoa saat Jukka Korpelan kirjoituksesta Mikä on merkki)

Kauan sitten luotiin koodaus nimeltä ASCII, jolla englanninkielistä perustekstiä sai kirjoitettua. Ääkkösten ja muiden kielten erikoisempien kirjainten vuoksi ascii-koodausta laajennettiin, ja tuloksena oli suunnilleen suomenkielinen koodaus, tanskankielinen koodaus jne. Tämä oli ongelma ja on sitä usein edelleen.

Lisäksi tietokoneella halutaan usein saada sanoja aakkosjärjestykseen, joka sekin on kieliriippuvaista. Grepin kannalta tällä on merkitystä vain hakasulkumerkinnöissä, siis siinä miten [x-y] -tyyppiset merkinnät käsitellään.

Oikein asennetussa järjestelmässä pitäisi kielen määrittelyyn riittää yksi komento, esimerkiksi Fedora Core 2:ssa
export LANG=finnish
Muissa järjestelmissä komento voi olla
export LANG=fi_FI
tai, jos komentotulkkisi on tcsh, vaikkapa
setenv LANG fi_FI
Sopivan locale-asetuksen löydät luultavasti komennolla
locale -a | fgrep 'fi'

Näet kielimäärittelyn eron esimerkiksi komennoilla
export LANG=C
echo 'abcdABCD' | egrep -o '[b-c]'
export LANG=finnish
echo 'abcdABCD' | egrep -o '[b-c]'

Lisätietoa locale-asetuksista tarjoaa Ari Mäkelän kirjoittama Finnish-HOWTO, joka nimestään huolimatta on suomenkielinen.


Kirjoittanut Jori Mäntysalo, email jori.mantysalo@uta.fi. Viimeksi päivitetty 2004-07-29.