Kirjautuminen

Haku

Tehtävät

Opasarkisto: PHP: Säännölliset lausekkeet PHP:ssä

Kirjoittaja: Antti Laaksonen. Vuosi: 2005.

Kokeneiden PHP-ohjelmoijien skripteissä esiintyy usein merkillisen näköisiä koodirivejä, jotka saavat pituuteensa nähden paljon aikaan. Yksi ainoa huolellisesti muodostettu koodirivi saattaa esimerkiksi muuttaa kaikki tekstissä olevat nettiosoitteet toimiviksi HTML-linkeiksi. Vastaava onnistuisi mainiosti myös perinteisillä merkkijonofunktioilla (substr, strpos), mutta kirjoitettavaa olisi silloin monin verroin enemmän. Siksi säännöllisiä lausekkeita (regular expressions, regexp), jotka etsivät tai korvaavat merkkijonossa tiettyä kaavaa noudattavat tekstinpätkät, kannattaa käyttää aina sopivan tilaisuuden tullen. PHP tukee kahdenlaisia säännöllisiä lausekkeita, joista tähän oppaaseen on valittu monipuolisemmat Perl-kielestä lainatut lausekkeet.

Funktioiden esittely

Säännöllisiin lausekkeisiin liittyvät funktiot ovat:

FunktioKäyttötarkoitus
preg_greperottaa taulukosta säännönmukaiset merkkijonot
preg_matchetsii merkkijonosta ensimmäisen sääntöön sopivan tekstinpätkän
preg_match_alletsii merkkijonosta kaikki sääntöön sopivat tekstinpätkät
preg_quotelisää säännöllisten lausekkeiden erikoismerkkien eteen kenoviivan
preg_replacekorvaa merkkijonon sääntöön sopivat tekstinpätkät toisella
preg_replace_callbackvälittää tekstinpätkät erilliselle korvauksen muodostavalle funktiolle
preg_splitmuodostaa merkkijonosta taulukon säännölliset lausekkeet erottimina

Yksittäiset merkit

Seuraavassa esimerkissä preg_replace-funktiota on käytetty aivan str_replace-funktion tapaan. Ainoana erona on korvattavan merkkijonon alussa ja lopussa olevat /-merkit, jotka pitää kirjoittaa säännöllisen lausekkeen ympärille.

<?php
echo preg_replace("/2004/", "2005", "Nyt on vuosi 2004");
// Nyt on vuosi 2005
?>

Useimmat merkit kirjoitetaan sellaisenaan säännölliseen lausekkeeseen. Kuitenkin muutamien merkkien eteen pitää panna kenoviiva (\), koska niillä on muuten erikoistehtävä. Erikoismerkit ovat: /\.^$[]|()+*?!{}=:<>. Jos säännöllinen lauseke on PHP-skriptissä lainausmerkkien ympärillä, kenoviivoja pitää tässä tapauksessa kirjoittaa kaksi.

<?php
echo preg_replace("/\\?/", "!", "Toimii?");
// Toimii!
?>

Tavallisesti suurten ja pienten kirjainten välille tehdään ero, mutta säännöllisen lausekkeen perään laitettava i-kirjain poistaa tämän erottelun.

<?php
if (preg_match("/mysql/i", "MySQL")) echo "Sana löytyi.";
// Sana löytyi.
?>

Eräänlaisena yleismerkkinä toimii piste, joka vastaa mitä tahansa merkkiä rivinvaihtoa lukuun ottamatta. Lisäksi \w tarkoittaa aakkosmerkkiä ja \d numeromerkkiä. Tässä esimerkissä nimen täytyy olla viisikirjaiminen, alkaa A:lla ja päättyä i:hin.

<?php
if (preg_match("/A...i/", "Antti")) echo "Nimi kelpaa.";
// Nimi kelpaa.
?>

Alku ja loppu

Edellisen esimerkin tarkistus ei ole aivan aukoton. Se kyllä kelpuuttaa kaikki oikeanmuotoiset nimet, mutta myös koko joukon vääriä. Esimerkiksi nimet Juho-Anssi ja Amalia menevät täydestä. Syynä on se, että etsittävä tekstinpätkä saa olla missä tahansa kohden merkkijonoa. Korjaus on käyttää merkkijonon aloitus- (^) ja lopetusmerkintöjä ($) tähän tapaan:

<?php
if (preg_match("/^A...i$/", "Antti")) echo "Nimi kelpaa.";
// Nimi kelpaa.
?>

Jos säännöllisen lausekkeen jälkeen on m-kirjain, myös rivin alku ja loppu kelpaavat. Nyt riittää, että etsittävä nimi on omalla rivillään.

<?php
if (preg_match("/^A...i$/m", "Pekka\nAntti\nJohanna")) echo "Nimi kelpaa.";
// Nimi kelpaa.
?>

Merkintä \b tarkoittaa, että sen paikalla ei saa olla aakkosmerkkiä. Tämän merkinnän avulla pystyy tunnistamaan kokonaisia sanoja. Seuraavassa esimerkissä ainoastaan puu-sana lähtee pois, vaikka kirjaimet esiintyvät myös muissa sanoissa.

<?php
echo preg_replace("/\\bpuu\\b/", "", "puute puu kaipuu");
// puute kaipuu
?>

Merkkiryhmät

Hakasulkujen sisään kirjoitetuista merkeistä kelpaa mikä tahansa. Seuraavassa esimerkissä kaikki vokaalit muuttuvat kysymysmerkeiksi.

<?php
echo preg_replace("/[AEIOUYÅÄÖaeiouyåäö]/", "?", "Auto kääntyi pihaan.");
// ??t? k??nt?? p?h??n.
?>

Peräkkäistä kirjain- tai numerosarjaa ei tarvitse kirjoittaa kokonaisuudessaan, vaan ensimmäinen ja viimeinen merkki ja väliviiva riittävät. Esimerkiksi merkintä [a-zA-Z0-9] tarkoittaa isoja ja pieniä kirjaimia a – z ja numeroita. Seuraava tarkistus hyväksyy kaikki shakkipelin ruudut (ensin kirjain a – h, sitten numero 1 – 8).

<?php
if (preg_match("/^[a-h][1-8]$/", "b5")) echo "Ruutu kelpaa.";
// Ruutu kelpaa.
?>

Jos heti aloittavan hakasulun jälkeen tulee hattumerkki (^), kaikki muut merkit kelpaavat paitsi mainitut. Tässä esimerkissä kyy ja pyy joutuvat pois.

<?php
echo preg_replace("/[^s]yy/", "", "kyy pyy syy");
//  syy
?>

Vaihtoehdot

Pystyviivoilla erotetuista tekstinpätkistä kelpaa mikä tahansa.

<?php
echo preg_replace("/hintava|kallis|tyyris/", "halpa", "Auto on kallis.");
// Auto on halpa.
?>

Jos vaihtoehdot meinaavat sekoittua muuhun tekstiin, ne pitää ympäröidä suluilla. Tämä tarkistus edellyttää, että tiedoston nimi päättyy pisteeseen ja kuvamuodon tunnukseen.

<?php
if (preg_match("/\\.(png|jpg|gif)$/", "oma.jpg")) echo "Kuva kelpaa.";
// Kuva kelpaa.
?>

Toisto

Yksittäisen merkin, merkkiryhmän tai vaihtoehdon perässä seuraavat merkinnät tarkoittavat toistoa:

MerkintäToisto
*vähintään nolla kertaa
+vähintään yhden kerran
?ei kertaakaan tai kerran
{m}tasan m kertaa
{v,}vähintään v kertaa
{v,k}vähintään v kertaa, enintään k kertaa

Tässä esimerkissä kaikki numerosarjat muuttuvat risuaidoiksi.

<?php
// vaatimus: erillinen sana, jossa on yksi tai useampi numeromerkki
echo preg_replace("/\\b[0-9]+\\b/", "#", "talo 77, kerros 3, huone 218");
// talo #, kerros #, huone #
?>

Seuraava tarkistus hyväksyy päivämäärää muistuttavat merkkijonot, joissa on pisteillä erotettuina kaksi yhden tai kahden numeron osuutta ja viimeisenä neljän numeron osuus. Tarkistusta voisi parantaa rajaamalla tarkemmin päivän ja kuukauden ensimmäisen numeron, mutta täysin luotettavaa tarkistusta säännöllisillä lausekkeilla ei pysty tekemään.

<?php
if (preg_match("/^[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}$/", "24.2.2005")) {
    echo "Päivämäärä kelpaa.";
}
// Päivämäärä kelpaa.
?>

Jos toistomerkinnät on kirjoitettu sellaisenaan, ne yrittävät löytää mahdollisimman pitkän sopivan tekstinpätkän. Jos taas merkintöjen perään on laitettu kysymysmerkki, ne tyytyvät mahdollisimman vähään. Seuraavassa esimerkissä tarkoituksena on poistaa suluissa olevat tekstit. Ilman kysymysmerkkiä poistettava osuus alkaa ensimmäisestä sulusta ja päättyy viimeiseen, minkä takia tekstiä katoaa liikaa.

<?php
echo preg_replace("/\\(.*\\)/", "", "2 (kaksi) 3 (kolme)");
// 2
echo preg_replace("/\\(.*?\\)/", "", "2 (kaksi) 3 (kolme)");
// 2  3
?>

Jatkokäsittely

Tekstin korvauksessa olisi monesti kätevää hyödyntää säännölliseen lausekkeeseen sopinutta tekstinpätkää myös korvausvaiheessa. Tämä onnistuu kirjoittamalla \0-merkintä korvauksen ilmoittavaan merkkijonoon. Seuraavassa esimerkissä kaikki numerosarjat muuttuvat lihavoiduiksi.

<?php
echo preg_replace("/\\b[0-9]+\\b/", "<b>\\0</b>", "talo 77, kerros 3, huone 218");
// talo <b>77</b>, kerros <b>3</b>, huone <b>218</b>
?>

Myös säännöllisen lausekkeen osiin pääsee käsiksi kirjoittamalla ne sulkujen sisään. Koko lauseketta kuvaavan \0:n lisäksi käytössä on silloin merkinnät \1, \2, \3 jne., joissa on järjestyksessä suluissa olleet osuudet. Tässä esimerkissä kymmentä kirjainta pidemmät sanat lyhennetään, ja niiden perään tulee kolme pistettä.

<?php
// vaatimus: kymmenen kirjainta, joiden jälkeen yksi tai useampi kirjain
// \1 viittaa suluissa olevaan osuuteen eli kymmeneen kirjaimeen
echo preg_replace("/(\w{10})\w+/", "\\1...", "kynä matkapuhelin tietosanakirja");
// kynä matkapuhel... tietosanak...
?>

Aikaisemmin löytyneitä lausekkeen osia on mahdollista hyödyntää myös varsinaisessa säännöllisessä lausekkeessa. Seuraava tarkistus kelpuuttaa kaikki numerosarjat, joiden jokainen numero on sama.

<?php
// vaatimus: ensimmäinen numero väliltä 0 - 9, loput samaa numeroa
if (preg_match("/^([0-9])\\1*$/", "333333")) echo "Numerosarja kelpaa.";
// Numerosarja kelpaa.
?>

Tyylikkäitä korvauksia

Funktiolla preg_replace_callback saa syötettyä korvattavan osan merkkijonosta toiselle funktiolle samanlaisena taulukkona, kuin preg_match antaa tuloksen. Tämä mahdollistaa monenlaiset tyylikkäät korvaukset. Seuraavassa tekstissä esiintyvät murtoluvut muutetaan desimaaliluvuiksi.

<?php
function muuta_murtoluku($m) {
    return round($m[1] / $m[2], 3);
}
// vaatimus: kaksi numerosarjaa, joiden välissä on jakomerkki
echo preg_replace_callback("/([0-9]+)\/([1-9][0-9]*)/", "muuta_murtoluku", "Tulokset: 6/17 ja 10/21.");
// Tulokset: 0.353 ja 0.476.
?>

Tämä esimerkki sekoittaa sanojen keskellä olevat kirjaimet.

<?php
function sekoita_sana($m) {
    return $m[1] . str_shuffle($m[2]) . $m[3];
}
// vaatimus: kaksi kirjainta, joiden välissä voi olla lisää kirjaimia
$teksti = "Saatko selvää tällaisesta tekstistä?";
echo preg_replace_callback("/(\w)(\w*)(\w)/", "sekoita_sana", $teksti);
// Saktao slevää ttelaiäslsa titekstsä?
?>

Loppusanat

Tässä on lopuksi muutamia hyviä säännöllisiin lausekkeisiin liittyviä Internet-sivustoja (englanniksi):

Voit lähettää oppaaseen liittyvää palautetta ja kysymyksiä sähköpostilla tai sivun alalaidassa olevan lomakkeen kautta.

PS. Suurin osa oppaan tiedoista pätee myös muissa säännöllisiä lausekkeita ymmärtävissä ohjelmointikielissä!

Kommentit

Tumpi [06.01.2005 17:51:48]

Lainaa #

Kiva. :)

peki [06.01.2005 22:25:13]

Lainaa #

Kiitoksia. Olen kaivannutkin tällaista.

mozak [08.01.2005 19:40:38]

Lainaa #

Loistava opas jälleen kerran!

sooda [18.01.2005 18:24:41]

Lainaa #

Joskus etsin ymmärrettävää regexp-opasta niin pitkään että tyydyin tekemään purkkakoodia sen sijasta että oisin tuhlannut tunneittain aikaa ettimiseen. Olen aina halunnut tietää miten regexpit toimii. Uskomattoman hyödykäs ja helppotajuinen opas =)

cj [18.01.2005 21:05:14]

Lainaa #

Onpas hyvä ;) tälläistä olen kaivannut. Nyt ei enää ole temppukaan tehdä hyvää hakuskriptiä :D

ZcMander [19.01.2005 18:19:59]

Lainaa #

Ihanaa, tälläista toisiaan on kaivattu.

Gwaur [23.04.2006 00:14:13]

Lainaa #

Vinkki: Jos preg_matchin ja preg_replacen parametrimerkkijonot pistää yksittäisten heittomerkkien sisään niin kuin minkä tahansa merkkijonon voi, kenoviivoja ei tarvitse laittaa aina kahta, koska yksittäisheittomerkkimerkkijonoja PHP ei parseroi. Tällöin myös nopeus kasvaa.

Juhko [04.01.2010 21:18:39]

Lainaa #

Katos, juuri tätä tarvitsinkin :)

Le-Co-Las [03.03.2010 06:18:05]

Lainaa #

Yhdyn mielelläni edelliseen..............
....kirjoitukseen. Kiitos tömä tuli tarpeeseen, kiitos myös Tomppa32:lle ja Tietty tämän laatijalle Antille!!

dartvaneri [14.12.2010 23:45:23]

Lainaa #

Miten saisi tehtyä sellaisen että se tulostaa tiedoston päätten?
Esim.

Tiedosto ______Tulostus
etusivu.php ___.php
testi.html ____.html
kuva.png _____ .png


Kiitos jo etukäteen!

AkeMake [13.01.2011 16:15:11]

Lainaa #

Aivan mahtava opas. Hyvin neuvottiin miten saa tunnistettua kokonaisia sanoja. Haluaisin nyt kuitenkin leikata tekstistä esim. viisi ensimmäistä sanaa erilleen samalla tavoin kuin Ohjelmointiputkan uusimman keskusteluviestin näyttävässä laatikossa. Miten toimitaan?

Koetin tällaista viritelmää, muttei se näytä toimivan:

$teksti = "Tässä tekstissä on yli viisi sanaa, joten viidennen sanan jälkeen tulee pisteitä ja lue lisää -linkki.";
$teksti = preg_replace("/(\\b[a-zA-Z0-9åäöÅÄÖ]+\\b)\\1{5}/", "\\0... <a href='#' title='Lue lisää'>Lue lisää</a>", $teksti);
// Pitäisi näyttää tältä:
// Tässä tekstissä on yli viisi... Lue lisää

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta