Ero sivun ”Säännöllinen lauseke” versioiden välillä

Siirry navigaatioon Siirry hakuun
1 283 merkkiä lisätty ,  9. syyskuuta 2020
Selvennetty muutamia lyhenteitä.
(ehostus)
(Selvennetty muutamia lyhenteitä.)
 
(12 välissä olevaa versiota 6 käyttäjän tekeminä ei näytetä)
Rivi 1: Rivi 1:
'''Säännöllinen lauseke''' (engl. '''regular expression''', '''regexp''') on yksinkertainen merkkijonokieli, joka voi joko ''vastata'' tai ''olla vastaamatta'' jotain toista merkkijonoa.  Linux- ja Unix-järjestelmissä säännöllisiä lauskkeista on suurta hyötyä [[komentorivi]]ä käytettäessä esimerkiksi merkkijonojen etsimis- ja korvaustoiminnoissa. Niiden voidaankin sanoa olevan [[putki]]ttamiseen yhdistettynä tärkeimpiä työkaluja, joita komentorivin edistynyt käyttö edellyttää.
'''Säännöllinen lauseke''' (engl. '''regular expression''', '''regexp''') on yksinkertainen merkkijonokieli, joka voi joko ''vastata'' tai ''olla vastaamatta'' jotain toista merkkijonoa.  Linux- ja Unix-järjestelmissä säännöllisiä lausekkeista on suurta hyötyä [[komentorivi]]ä käytettäessä esimerkiksi merkkijonojen etsimis- ja korvaustoiminnoissa. Niiden voidaankin sanoa olevan [[putki]]ttamiseen yhdistettynä tärkeimpiä työkaluja, joita komentorivin edistynyt käyttö edellyttää.


Tässä artikkelissa on esitetty johdatus säännöllisiin lausekkeisiin siten, miten komentorivityöklu <tt>[[grep]]</tt> ne ymmärtää ''laajennettussa'' (engl. extended) tilassa. Kaikissa säännöllisten lausekkeiden toteutuksissa syntaksi ei kuitenkaan ole aivan sama. Perusoperaattorit (<tt>*</tt>, <tt>+</tt>, <tt>?</tt>, sulut) ovat kuitenkin ''universaaleja'', eli kaikki toteutukset tukevat niitä.
Tässä artikkelissa on esitetty johdatus säännöllisiin lausekkeisiin siten, miten komentorivityökalu <tt>[[grep]]</tt> ne ymmärtää ''laajennetussa'' (engl. extended) tilassa. Perusoperaattorit (<tt>*</tt>, <tt>?</tt>, sulut) ovat ''universaaleja'', eli kaikki toteutukset tukevat niitä. Kaikissa säännöllisten lausekkeiden toteutuksissa syntaksi ei kuitenkaan ole aivan sama.  


Säännöllisistä lausekkeista on yleisesti käytössä kolme tyyppiä: ''tavallinen'' (engl. basic), laajennettu (engl. extended) ja ''[[Perl]]-tyyppinen''. Varsinkin useassa järjestelmässä käytettäviä [[skripti|skriptejä]] kirjoitettaessa pitää olla huolellinen, mikäli käyttää tavallisesta poikkeavia ilmaisuja. Tässä ohjeessa esitetty syntaksi on yhteensopiva Linuxin [[GNU]]-grepin laajennetun syntaksin kanssa jollei toisin mainita.
Säännöllisistä lausekkeista on yleisesti käytössä kolme tyyppiä: ''tavallinen'' (engl. basic), ''laajennettu ''(engl. extended) ja ''[[Perl]]-tyyppinen''. Tässä ohjeessa esitetty syntaksi on yhteensopiva Linuxin [[GNU]]-grepin laajennetun syntaksin kanssa jollei toisin mainita. GNU grepin syntaksi taas on yhteensopiva [[POSIX]]-standardin mukaisten säännöllisten lausekkeiden kanssa. Varsinkin useassa järjestelmässä käytettäviä [[skripti|skriptejä]] kirjoitettaessa täytyy olla huolellinen siinä, mitkä rakenteet ovat uudempia laajennoksia. Erotuksena grep myös käsittelee syötettä rivi riviltä. Toisissa ohjelmissa saatetaan käsitellä rivinvaihtoa tavallisena merkkinä.


Säännöllisiä lausekkeita ei pidä sekoittaa tiedostonimi-jokereihin ([[glob]]), joilla on samankaltainen toiminnallisuus (vrt. <tt>*</tt> ja <tt>.*</tt>).
Säännöllisiä lausekkeita ei tule sekoittaa tiedostonimi-jokereihin (glob), joilla on samantapainen toiminnallisuus (vrt. <tt>*</tt> ja <tt>.*</tt>). Tiedostonimijokereita ja säännöllisiä lausekkeita voi hyödyntää usein samoissa yhteyksissä, mutta säännölliset lausekkeet ovat tiedostonimijokereita ilmaisuvoimaisempia.


== Teoriaa ==
== Teoriaa ==
Tietojenkäsittelytieteessä puhutaan [[wikipedia:fi:säännöllinen kieli|säännöllisestä kielestä]], joka voidaan tunnistaa [[wikipedia:fi:äärellinen automaatti|äärellisellä automaatilla]]. Säännöllisten lauskkeiden historia juontaa juurensa siten 50- ja 60-luvuille [[wikipedia:fi:tietojenkäsittelytiede|tietojenkäsittelytieteen]] syntyaikohiin. [[Unix]]-järjestelmissä säännöllisiä lausekkeita on voinut hyödyntää aina ensimmäisistä versioista lähtien. Ensimmäinen grep-ohjelma kirjoitettiin ilmeisesti vuonna 1973.<sup>[http://www.columbia.edu/~rh120/ch001j.c11]</sup>
[[wikipedia:fi:Tietojenkäsittelytiede|Tietojenkäsittelytieteessä]] säännöllisiä lausekkeita käsitellään [[wikipedia:fi:säännöllinen kieli|säännöllisten kielten]] teoriassa. Säännöllinen lauseke on esimerkki [[wikipedia:fi:äärellinen automaatti|äärellisestä automaatista]]. Säännöllisten lausekkeiden historia juontaa juurensa 50- ja 60-luvuille tietojenkäsittelytieteen syntyaikoihin.


Säännöllisiä lausekkeita voi hyödyntää monissa ohjelmointikielissä (mm. Perl, Java, Python, ECMAScript). Mille tahansa [[wikipedia:en:Turing complete|Turing-täydelliselle]] ohjelmointikielelle voidaan myös aina kirjoittaa säännöllisten lausekkeiden [[wikipedia:fi:Ohjelmointikielen tulkki|tulkki]].
Säännöllisiä lausekkeita voi hyödyntää monissa ohjelmointikielissä (mm. Perl, Java, Python, ECMAScript). Mille tahansa [[wikipedia:en:Turing complete|Turing-täydelliselle]] ohjelmointikielelle voidaan myös aina kirjoittaa säännöllisten lausekkeiden [[wikipedia:fi:Ohjelmointikielen tulkki|tulkki]].
== Historia ==
Ensimmäisenä säännöllisten lausekkeiden toteutuksena aidolle tietokoneelle (IBM 7094) pidetään Unix-pioneeri [[wikipedia:fi:Ken Thompson|Ken Thompson]]in julkaisua [http://portal.acm.org/citation.cfm?doid=363347.363387 Regular Expression Search Algorithm] vuodelta 1968. [[Unix]]-järjestelmissä säännöllisiä lausekkeita on siten voinut hyödyntää aina ensimmäisistä versioista lähtien. Ensimmäinen [[grep]]-ohjelma kirjoitettiin ilmeisesti vuonna 1973.<sup>[http://www.columbia.edu/~rh120/ch001j.c11]</sup>


== Johdatus säännöllisiin lausekkeisiin ==
== Johdatus säännöllisiin lausekkeisiin ==
Rivi 24: Rivi 27:
  bono
  bono


Yleensä (mm. grep ja awk) katsotaan, että kohteen alussa ja lopussa voi olla mielivaltainen määrä ei-vastaavia merkkejä. Tällöin kohde (s.o. näiden komentojen tapauksessa ''rivi'') vastaa lauseketta, jos edes jokin sen ''osa'' vastaa lauseketta. Siis grep ja awk -yhteydessä lauseke
Yleensä (mm. grep ja awk) katsotaan, että kohteen alussa ja lopussa voi olla mielivaltainen määrä ei-vastaavia merkkejä. Tällöin kohde (eli näiden komentojen tapauksessa jokin ''rivi'' jossain tiedostossa) vastaa lauseketta, jos edes jokin sen ''osa'' vastaa lauseketta. Siis grep ja awk -yhteydessä lauseke
  abba
  abba
vastaa kohdetta
vastaa kohdetta
Rivi 66: Rivi 69:
  bba
  bba


Todettakooon, että selvästi b-merkkien määrä kohteessa voi olla mielivaltainen, joten eri vastaavia kohdemerkkijonoja on olemassa myös ääretön määrä.
Todettakoon, että selvästi b-merkkien määrä kohteessa voi olla mielivaltainen, joten eri vastaavia kohdemerkkijonoja on olemassa myös ääretön määrä.


=====Vähintään yksi: <tt>+</tt>=====
=====Vähintään yksi: <tt>+</tt>=====
Rivi 80: Rivi 83:
  '''a'''cccca
  '''a'''cccca
  bba
  bba
Lausekkeen x+ voi kirjoittaa myös xx*, jolloin <tt>+</tt>-operaattoria ei tarvitse käyttää (se ei sisälly kaikkiin "tavallisen" syntaksin toteutuksiin).


=====Ehdollinen: <tt>?</tt>=====
=====Ehdollinen: <tt>?</tt>=====
Rivi 113: Rivi 118:
tarkoittaa ''"korkeintaan m kertaa"''.
tarkoittaa ''"korkeintaan m kertaa"''.


Sivuhuomatuksena todettakoon, että yllä olevan perusteella lyhyemmät operaattorit <tt>*</tt>, <tt>+</tt> ja <tt>?</tt> voitaisiin aina korvata ilmaisuilla <tt>{0,}</tt>, <tt>{1,}</tt> ja <tt>{0,1}</tt> vastaavasti. {m,n}-muoto on kuitenkin uudempi laajennos, joka ei toimi kaikissa ohjelmassa.
Sivuhuomautuksena todettakoon, että yllä olevan perusteella lyhyemmät operaattorit <tt>*</tt>, <tt>+</tt> ja <tt>?</tt> voitaisiin aina korvata ilmaisuilla <tt>{0,}</tt>, <tt>{1,}</tt> ja <tt>{0,1}</tt> vastaavasti. {m,n}-muoto on kuitenkin uudempi laajennos, joka ei toimi kaikissa ohjelmassa.


'''Huom!''' Operaattori <tt>{n,m}</tt> ei ole tuettu kaikissa säännöllisten lausekkeiden toteutuksissa. GNU grep tukee niitä laajennetussa tilassa (<tt>egrep</tt>).
'''Huom!''' Operaattori <tt>{n,m}</tt> ei ole tuettu kaikissa säännöllisten lausekkeiden toteutuksissa. GNU grep tukee niitä laajennetussa tilassa (<tt>egrep</tt>).
Rivi 133: Rivi 138:
  '''hu'''aa!!
  '''hu'''aa!!
  '''hu'''uuurraa!!
  '''hu'''uuurraa!!
  '''hurr'''urrur!!
  '''hurr'''urrur!! // miksi ei vastaa???
jne.
jne.


Rivi 258: Rivi 263:
  [thlTHL]upu
  [thlTHL]upu


Hakasulkujen välissä voidaan myös määritellä [[lokaali]]n aakkoston mukaisia välejä väliviivalla <tt>-</tt>. Tällöin  
Hakasulkujen välissä voidaan myös määritellä [[lokaali]]n aakkosjärjestyksen (LC_COLLATE) mukaisia välejä väliviivalla <tt>-</tt>. Tällöin  
  19[4-9][0-9]|20[0-9][0-9]
  19[4-9][0-9]|20[0-9][0-9]
vastaa kaikkia vuosilukuja välillä 1940-2099. Samoin  
vastaa kaikkia vuosilukuja välillä 1940-2099. Samoin  
Rivi 272: Rivi 277:
  '''Osasto '''J
  '''Osasto '''J


On huomioitavaa, että myös kohteet ”Osasto b” tai ”Osasto È” saattavat vastata yllä esitettyä lausketta joissain [[lokaali|lokaaleissa]].
On huomioitavaa, että myös kohteet ”Osasto b” tai ”Osasto È” saattavat vastata yllä esitettyä lauseketta joissain [[lokaali|lokaaleissa]].


Jos merkki <tt>-</tt> halutaan sisällyttää hakasulkuilmaisuun, se jätetään viimeiseksi. Esim.
Jos merkki <tt>-</tt> halutaan sisällyttää hakasulkuilmaisuun, se jätetään viimeiseksi. Esim.
Rivi 292: Rivi 297:
  zo
  zo


Hakasulkuilmaisusn merkitys voidaan kääntää asettamalla hattu <tt>^</tt> sen ensimmäiseksi merkiksi. Tällöin hakasulkuilmaisu vastaa mitä tahansa hakasuluissa <u>'''ei'''</u> esiintyvää merkkiä kohteessa. Täten lauseke
Hakasulkuilmaisun merkitys voidaan kääntää asettamalla hattu <tt>^</tt> sen ensimmäiseksi merkiksi. Tällöin hakasulkuilmaisu vastaa mitä tahansa hakasuluissa <u>'''ei'''</u> esiintyvää merkkiä kohteessa. Täten lauseke
  [^aeiouyåäö]+
  [^aeiouyåäö]+
vastaa mitä tahansa pelkistä konsonanteista, välimerkeistä ja numeroista koostuvaa kohdetta, kuten
vastaa mitä tahansa pelkistä konsonanteista, välimerkeistä ja numeroista koostuvaa kohdetta, kuten
Rivi 317: Rivi 322:
Merkkiluokat tulee laittaa lisäksi ulompien hakasulkujen <tt>[]</tt> sisään. Esimerkiksi  
Merkkiluokat tulee laittaa lisäksi ulompien hakasulkujen <tt>[]</tt> sisään. Esimerkiksi  
  <nowiki>[[:upper:]]</nowiki>+
  <nowiki>[[:upper:]]</nowiki>+
joka vastaa yleensä ilmaisua
joka vastaa C-lokaalilla (LC_CTYPE=C) ilmaisua
  [A-Z]+
  [A-Z]+
eli mielivaltainen (vähintään yksi) määrä isoja kirjaimia A-Z.
eli mielivaltainen (vähintään yksi) määrä isoja kirjaimia A-Z.
Rivi 345: Rivi 350:


=== Rivin alku ja loppu: <tt>^</tt> ja <tt>$</tt>===
=== Rivin alku ja loppu: <tt>^</tt> ja <tt>$</tt>===
On voinut herätä kysymys, kuinka voidaan vastata kohdetta (s.o. riviä) ''tarkalleen''. Heti alussa selvisi, että esim grepin mielestä kohde (rivi) vastaa lauseketta, jos lauseke esiintyy missä tahansa kohdassa riviä. Jos halutaan, että lauseke vastaa alusta loppuun tarkalleen koko riviä, on otettava käyttöön erikoismerkit <tt>^</tt> ja <tt>$</tt>. Nämä vastaavat kohteessa rivin tai merkkijonon alkua ja loppua kuvaavia "näkymättömiä" merkkejä vastaavasti. Siten
On voinut herätä kysymys, kuinka voidaan vastata kohdetta (eli riviä) ''tarkalleen''. Heti alussa selvisi, että esim grepin mielestä kohde (rivi) vastaa lauseketta, jos lauseke esiintyy missä tahansa kohdassa riviä. Jos halutaan, että lauseke vastaa alusta loppuun tarkalleen koko riviä, on otettava käyttöön erikoismerkit <tt>^</tt> ja <tt>$</tt>. Nämä vastaavat kohteessa rivin tai merkkijonon alkua ja loppua kuvaavia "näkymättömiä" merkkejä vastaavasti. Siten
  ^abba$
  ^abba$
vastaa vain riviä
vastaa vain riviä
Rivi 385: Rivi 390:
  (... jne ...)
  (... jne ...)


Jos haluamme ylläolevasta listasta vain "qmail" -alkuisten [[käyttäjä|käyttäjien]] prosessit, voisimme kirjoittaa
Jos haluamme ylläolevasta listasta vain "qmail" -alkuisten [[käyttäjä|käyttäjien]] prosessit, voisimme [[putki]]ttaa syötteen grepille kirjoittamalla
  ps -ef | grep qmail
  ps -ef | grep qmail
Tämä kuitenkin tulostaa myös kaikki sellaiset prosessit, joiden nimikentässä esiintyy "qmail" – joukossa myös käyttäjän [[root]] prosesseja vastoin alkuperäistä tarkoitusta:
Tämä kuitenkin tulostaa myös kaikki sellaiset prosessit, joiden nimikentässä esiintyy "qmail" – joukossa myös käyttäjän [[root]] prosesseja vastoin alkuperäistä tarkoitusta:
  $ ps -ef | grep qmail
  $ ps -ef | grep qmail
  '''qmail'''s    3986    1  0 Jan21 ?        00:00:00 '''qmail'''-send
  '''qmail'''s    3986    1  0 Jan21 ?        00:00:00 qmail-send
  '''qmail'''l    3990  3986  0 Jan21 ?        00:00:00 splogger '''qmail''' 2
  '''qmail'''l    3990  3986  0 Jan21 ?        00:00:00 splogger qmail 2
  root      3993  3986  0 Jan21 ?        00:00:00 '''qmail'''-lspawn | /usr/bin/deliverquota ./Maildir
  root      3993  3986  0 Jan21 ?        00:00:00 '''qmail'''-lspawn | /usr/bin/deliverquota ./Maildir
  '''qmail'''r    3994  3986  0 Jan21 ?        00:00:00 '''qmail'''-rspawn
  '''qmail'''r    3994  3986  0 Jan21 ?        00:00:00 qmail-rspawn
  '''qmail'''q    3995  3986  0 Jan21 ?        00:00:00 '''qmail'''-clean
  '''qmail'''q    3995  3986  0 Jan21 ?        00:00:00 qmail-clean


Ratkaisu qmail -alkuisten käyttäjien prosessien listaamiseen on:
Ratkaisu qmail -alkuisten käyttäjien prosessien listaamiseen on:
Rivi 405: Rivi 410:


===== Pako komentotulkista =====
===== Pako komentotulkista =====
Jos halutaan antaa jokin monimutkaisempi ilmaisu, on hyvä asettaa säännöllinen lauseke yksinkertaisten heittomerkkien <tt>'</tt> sisään, jotta ne regexp-operaattorit, joillla on jokin erikoismerkitys [[komentotulkki|komeotulkissa]] eivät aiheuta ongelmia.
Jos halutaan antaa jokin monimutkaisempi ilmaisu, on hyvä asettaa säännöllinen lauseke yksinkertaisten heittomerkkien <tt>'</tt> sisään, jotta ne regexp-operaattorit, joillla on jokin erikoismerkitys [[komentotulkki|komentotulkissa]] eivät aiheuta ongelmia.
  $ echo moi | egrep m(o|a)i
  $ echo moi | egrep m(o|a)i
  -bash: syntax error near unexpected token `('
  -bash: syntax error near unexpected token `('
Rivi 413: Rivi 418:


===== Laajennetut säännölliset lausekkeet: egrep =====
===== Laajennetut säännölliset lausekkeet: egrep =====
Esimerkissä käytettiin komentoa <tt>egrep</tt>. Se on oikopolku grepin [[valitsin|valitsimelle]] <tt>-E</tt>, joka ottaa säännöllisten lausekkeiden laajennetun (engl. extended) tuen käyttöön. Normaalissa käytössä (pelkkä komento <tt>grep</tt>) operaattorit <tt>? + {} | (</tt> ja <tt>)</tt> eivät ole käytettävissä, paitsi asettamalla niiden eteen pako-operaattorin <tt>\</tt>.
Esimerkissä käytettiin komentoa <tt>egrep</tt>. Se on oikopolku grepin [[valitsin|valitsimelle]] <tt>-E</tt>, joka ottaa säännöllisten lausekkeiden laajennetun (engl. extended) tuen käyttöön. Normaalissa käytössä (pelkkä komento <tt>grep</tt>) operaattorit <tt>? + {} | (</tt> ja <tt>)</tt> eivät ole käytettävissä, paitsi asettamalla niiden eteen pako-operaattorin <tt>\</tt>. Operaattoriella <tt>* . [] ^</tt> ja <tt>$</tt> on sama erikoismerkitys sekä käytettäessä normaalia (<tt>grep</tt>) että laajennettua (<tt>egrep</tt>) syntaksia, mutta esimerkiksi merkinnöillä <tt>\(</tt> ja <tt>(</tt> siis päinvastainen.


===== Prosessoitu tuloste: awk =====
===== Prosessoitu tuloste: awk =====
Rivi 434: Rivi 439:


== Laajennukset ==
== Laajennukset ==
Lukuunottamatta operaattoria <tt>{n,m}</tt> ja POSIX-luokkia (<tt>[[:luokka:]]</tt> jne.) yllä esitetty on pitkälti universaalisti tuettua säännöllisten lausekkeiden eri toteutuksissa. Monissa toteutuksissa on kuitenkin tehty laajennuksia tähän, mutta esim. [[grep]] ei tue seuraavia.  
Lukuunottamatta operaattoria <tt>{n,m}</tt> ja POSIX-luokkia (<tt>[[:luokka:]]</tt> jne.) yllä esitetty on pitkälti universaalisti tuettua säännöllisten lausekkeiden eri toteutuksissa. Monissa toteutuksissa on kuitenkin tehty laajennuksia tähän, mutta esim. <tt>[[grep]]</tt> ei tue seuraavia.  


=== Pakoluokat ===
=== Pakoluokat ===
Rivi 475: Rivi 480:
*[[wikipedia:fi:pinoautomaatti|Pinoautomaatti]]
*[[wikipedia:fi:pinoautomaatti|Pinoautomaatti]]
*[[wikipedia:fi:turingin kone|Turingin kone]]
*[[wikipedia:fi:turingin kone|Turingin kone]]
*[http://www.ohjelmointiputka.net/opas.php?tunnus=phpsl Säännölliset lausekkeet PHP:ssä] -opas Ohjelmointiputkassa
*[http://www.ohjelmointiputka.net/oppaat/opas.php?tunnus=php_16 Säännölliset lausekkeet PHP:ssä] -opas Ohjelmointiputkassa
*[http://swtch.com/~rsc/regexp/regexp1.html Regular Expression Matching Can Be Simple And Fast]: Keskustelua säännöllisten lausekkeiden toteutuksesta C-kielellä englanniksi.
*[http://swtch.com/~rsc/regexp/regexp1.html Regular Expression Matching Can Be Simple And Fast]: Keskustelua säännöllisten lausekkeiden toteutuksesta C-kielellä englanniksi.


Rekisteröitymätön käyttäjä

Navigointivalikko