Regular Expressions - Düzenli İfadeler

Bu dökümanın tamamı Tahribat.com için j4x (gVeR) tarafından yazılmıştır. Hiçbir alıntı yapılmamış olup, kod örnekleri de bizzat yazar tarafından uydurulmuştur. (Ç)alıntı yapmayın desem de iplemeyeceksiniz o yüzden demiyorum ama siz yine de yapmayın...

Regex Nedir ?


Regex (ya da Regexp), İngilizce Regular Expressions (Düzenli İfadeler) teriminin kısaltılmış halidir.

Çok kısaca özetlersek: Düzenli ifadeler, bir metnin belli bir formatta olup olmadığını kontrol etmek ve eğer gerekliyse belirli yerlerini ayıklamak için kullanılan desenlerdir.

Regex bir programlama dili değildir. Birçok programlama dili ve platform Regex eşleştirme ve ayıklama desteği sağlasa da programlamayla doğrudan alakalı birşey değildir.

Birden fazla Regex standardı olmasına rağmen bu dökümanda Regex'ten kasıt PCRE dir yani yalnızca PCRE (Perl Compatible Regular Expressions, Türkçesi: Perl Uyumlu Düzenli İfadeler) türü düzenli ifadeler hakkında bilgi içermektedir ve örnekler buna göre verilmiştir. Örneklerin verildiği programlama dili uygulamanın kolay olması açısından PHP olarak seçilmiştir. Eşdeğer fonksiyonları bulduktan sonra aynı desenleri .NET ve C -PCRE kütüphanesi kullanarak- gibi PCRE destekleyen platformlarda da kullanabilirsiniz.

Basit Bir Desen


Örneğin içinde 'Ata' kelimesi geçen bir metni Regex kullanarak tespit etmek istiyoruz. Bu durumda desenimiz /Ata/ olmalıdır. Bura da / ve / yerine Regex de özel bir anlamı olmayan herhangi bir karakter kullanılabilir, bu karakterler Delimiter (ayraç) görevindedir. Bu karakterleri eşleştirmek istediğimizde \ (ters bölü) işaretiyle escape etmeliyiz.

Örnek Php Uygulaması:

<?php
$metin = "Atatürk";
if (preg_match("/Ata/", $metin))
    print("Kelimenin icinde Ata kelimesi geçiyor!");
?>

Evet ilk örneğimizi yaptık. O kadar da zor gözükmüyor değil mi ? Yukarıda dikkat edilmesi gereken bir şey de büyük küçük harf duyarlılığıdır. Eğer deseni /ata/ olarak kullansaydık eşleşme olmayacaktı. Peki büyük küçük harf duyarlılığını nasıl kaldırırız ? Yukarıdaki örneği şu şekilde değiştirelim:

<?php
$metin = "Atatürk";
if (preg_match("/ata/i", $metin))
    print("Kelimenin icinde ata kelimesi geçiyor!");
?>


Farkettiyseniz desenin sonuna bir i harfi ekledik ve deseni /ata/i yaptık. Bu ne demektir? Neden delimiter (ayraç) dan sonra yazdık? Şimdi bu soruları cevaplayalım.

Buradaki i harfi bir modifier(değiştirici) dir. Regex'in eşleştirme yaparken bir takım kurallara uymasını ya da göz ardı etmesini söyler. Buradaki i harfi PCRE_CASELESS modifieridir ve desendeki büyük-küçük harf duyarlılığını kaldırır. Bunun gibi birçok modifier bulunur. Bunların bir kısmı dökümanın sonraki bölümlerinde açıklanacaktır.

Bu bölümde birşeyi daha açıklayalım. Yukarıdaki örnekteki deseni /a.a/i olarak değiştirelim.

<?php
$metin = "Atatürk";
if (preg_match("/a.a/i", $metin))
    print("Kelimenin icinde ata kelimesi geçiyor!");
?>


Uygulamayı çalıştırırsanız yine eşleşme sağlandığını göreceksiniz. Burada nokta (.) desen içinde özel anlam ifade eden bir karakterdir. Nokta karakteri (.) \n (new line / lf) karakteri hariç tüm karakterlerin yerine geçen bir joker karakterdir. Her nokta yalnızca 1 karakterin yerine geçer bunu da belirtelim. Yani /a./ deseni at ile eşleşir fakat ata ile eşleşmez.

İşleri Biraz Daha İleri Götürelim


Evet bir kelime içinde başka bir kelimeyi doğrudan aramak için Regex kullanmamıza gerek yoktu, bunu normal fonksiyonlar yardımıyla da yapabilirdik. Tabii ki Regex yalnızca yukarıdaki kullanım için geliştirilmedi. Şimdi işi biraz daha ilerletelim ve bir kelimenin içinde 0-9 arası bir rakam var mı diye bakalım.

<?php
$metin = "Merhaba ben 20 yaşındayım.";
if (preg_match("/[0-9]/", $metin))
    print("Kelimenin içinde rakam tespit edildi.");
?>


Yukarıdaki örnekte yeni birşey kullandık. Köşeli parantezler: [ ve ]. Bunlar bir karakter sınıfı oluşturmayı sağlarlar. Örneğin bir kelimenin içinde a, b ve c harflerinden en az biri geçiyor mu diye kontrol ettirmek istiyorsanız /[abc]/ desenini kullanabilirsiniz. Bunu istediğiniz kadar artırabilirsiniz. Aynı şekilde yukarıdaki örneği \[0123456789]\ şeklinde de yapıp aynı sonucu elde edebilirdiniz. Yani - karakteri bir karakter sınıfı içinde özel anlamı olan bir karakterdir ve bir aralık belirtir. Karakter sınıfları içerisinde sık kullanılan aralıklardan bazıları şunlardır:

[0-9]  0-9 arası rakamlar
[A-Z] A dan Z ye büyük harfler (İngilizce karakter setine göre)
[a-z] a dan z ye küçük harfler (İngilizce karakter setine göre)
[0-9A-Za-z] A dan Z ye büyük ve küçük harfler ve 0-9 arası rakamlar
[0-9A-F] Hexadecimal(16'lık) sayı sistemindeki sayılar


[0-9A-Za-z] örneğinden gördüğünüz gibi bu aralıklar birlikte kullanılabilmektedir. Aynı şekilde [0-9A-Za-z@!?] gibi kullanımlar da geçerlidir, büyük-küçük harfler ve rakamların yanı sıra @, ! ve ? işaretleriyle de eşleşir.

NOT: Karakter sınıfları içerisinde özel anlam ifade eden karakterler karakter sınıfları içerisinde kullanıldığında \ ile escape edilmelidir. Örneğin - karakteri. Ayrıca karakter sınıfları içerisinde özel anlamı olan karakterlerle karakter sınıfı dışında özel anlam ifade edenleri de karıştırmamak gerekir. Örneğin . karakteri bir karakter sınıfı içinde özel bir anlam ifade etmez. Aynı şekilde - karakteri de karakter sınıfı dışında normal bir karakterdir.

Miktar Belirteçleri


Şimdiye kadarki örneklerde hep tek karakter üzerinden eşleştirme yaptık. Fakat örneğin siz içinde 5 tane a harfi geçen bir metni tespit etmek istiyorsunuz. /aaaaa/ deseni yerine aşağıdaki gibi bir kullanım da doğru olacaktır.

<?php
$metin = "Bu cümlede tam 5 tane a harfi varmış.";
if (preg_match("/a{5}/i", $metin))
    print("Kelimenin icinde en az 5 tane a harfi bulunuyor.");
?>


Buradaki {5} bir quantifier(miktar belirteci) dir. Solundaki karakterden 5 tane aranacağı anlamına gelmektedir. Miktar belirteçleri sabit sayılarla sınırlı değildir, aşağıdaki tabloyu inceleyelim:

{X} X tane
{X,Y} En az X tane - en fazla Y tane
{X,} En az X tane
? 0 ya da 1 tane
* 0 ya da daha fazla
+ 1 ya da daha fazla


Bir şeyi daha anlattıktan sonra bununla ilgili bir örnek yapacağız...

Metnin Başı ve Sonu


Yukarıda ki örneklerde belirtilen desenlerin metnin herhangi bir yerinde olup olmadığı sınanmaktadır. Fakat siz metnin başından ya da sonundan aramaya başlamak isteyebilirsiniz. Bunun için 2 özel karakteri daha öğrenmeniz gerekecek.

^ karakteri: Metnin başlangıcını ifade eder. Örnek: /^abc/ deseni metnin ilk 3 harfinin abc olup olmadığını kontrol eder.

$ karakteri: Metnin sonunu ifade eder. Örnek: /xyz$/ deseni metnin son 3 harfinin xyz olup olmadığını kontrol eder.


Ayrıca metnin tamamının istediğiniz formatta olup olmadığını tespit etmek için iki özellikten birden yararlanabilirsiniz.

Örnek: /^Selahattin$/ bu desenle metnin 'Selahattin' kelimesine eşit olup olmadığını sınayabilirsiniz. Kısacası hem başlangıç hem sonu belirtince arada kalan metnin tamamı oluyor.


Şimdi daha işe yarar birşeyler yapmaya başlayabiliriz. Örneğin bir metnin tamamen rakamlardan oluşup oluşmadığını aşağıdaki gibi sınayabiliriz:

<?php
$metin = "12345";
if (preg_match("/^[0-9]+$/", $metin))
    print("Metin tamamen sayılardan oluşuyor.");
?>


Gördüğünüz gibi + miktar belirteciyle en az 1 rakam olup olmadığına bakıyoruz. Bunun yerine {1,} belirtecini de kullanabilirdik + onun kısayolu. Yine Başlangıç (^) ve Bitiş ($) işaretleri bir arada kullanılarak metnin tamamı kontrol ediliyor.

Olumsuz Karakter Sınıfları


Yukarıda karakter sınıflarından bahsetmiştik. [abc] a, b veya c harflerinden herhangi biriyle eşleşmekteydi. Peki ya bu harfler hariç herşeyle eşleşmesini isteseydik?

<?php
$metin = "Ben içinde rakam geçmeyen bir metinim.";
if (preg_match("/^[^0-9]+$/",$metin))
    print("Metnin içinde hiçbir sayı geçmiyor.");
?>


Örnekte gördüğünüz gibi [^0-9] "0-9 arası rakamlar hariç herhangi birşey" anlamına gelmektedir. Böylece karakter sınıfını olumsuzlaştırmış daha doğrusu tersine çevirmiş olduk. Burada tüm metnin "0-9 hariç herhangi birşey" den oluşmasını sağladık.

NOT: ^ karakteri bir karakteri sınıfı içerisinde olumsuzlaştırma görevindeyken karakter sınıfı dışında metin başlangıcını belirtme görevindedir. Bu ikisi karıştırılmamalıdır. Olumsuzlaştırma yapılırken ^ karakteri mutlaka karakter sınıfının içindeki ilk karakter olmalı, yani [ karakterinden hemen sonra gelmelidir.

Alternasyon


Evet başlık biraz garip oldu ama konu çok basit :). Bir eşleşme de birden fazla subpattern (alt desen) den herhangi birinin kabul edilmesini istiyorsak | karakterini yani alternasyon karakterini kullanırız.

Örneğin /abc|d/ deseni hem abc ile hem de abd ile eşleşecektir. Yani b ve d birbirinin alternatifidir. Yalnız buradaki problem; bu şekilde kullanımda yalnızca 1 karakterlik alternasyon yapılabilmektedir. İşte bu yüzden parantezlerin yani ( ve ) karakterlerinin kullanımı ve alt desenleri kısaca açıklayalım.

Aşağıdaki örneği inceleyiniz:

<?php
$metin1 = "Hasan nerelerdesin sen?";
$metin2 = "Hamit seni sormadım.";
if (preg_match("/ha(san|mit)/i", $metin1))
    print("Metnin içinde Hasan ya da Hamit kelimelerinden biri geçiyor.
");
if (preg_match("/ha(san|mit)/i", $metin2))
    print("Metnin içinde Hasan ya da Hamit kelimelerinden biri geçiyor.
");
?>


Gördüğünüz gibi 'h' ve 'a' harflerinden sonra 'san' ya da 'mit' ikisi de kabul ediliyor. eğer /hasan|mit/ şeklinde kullansaydık 'hasan' ve 'hasam' kelimeleriyle eşleşirdi. Çünkü gruplama olmadığı için yalnızca n ile m birbirine alternatif olacaktı. Kısacası buradaki parantezler alternasyonda gruplama yapmaya yarıyor ve bu gruplara alt desen diyoruz.

Opsiyonel Alt Desenler


Aslında yeni birşey değil, yukarıda miktar belirteçlerini anlatırken ? karakterinin özel bir karakter olduğunu ve "0 ya da daha fazla" anlamına geldiğini söyledik.

Hemen bir örnek verelim:

<?php
$metin = '<td align="center">tablo hücresi</td>';
if (preg_match('/<td( align="center")?>/', $metin))
    print("Td tagı eşleşti.");
?>


HTML <td> tagı align adında bir attribute(özellik) alabilmektedir. Ama bu özellik opsiyoneldir. Yukarıdaki örnekte desen, hem düz <td> tagıyla hem de <td align="center"> gibi align belirtilen bir tagla eşleşmektedir.

NOT: Yukarıdaki metinlerde ' (tek tırnak) kullanılmasının sebebi HTML tagındaki çift tırnaklarla PHP'dekilerin karışmasını önlemektir.

Escaping (Kaçış Sekansları)


Programlama dillerinde olduğu gibi Regex'te de özel anlamlı karakterleri escape ederek kullanabiliyorsunuz. Örneğin yukarıdaki $ ya da . gibi karakterleri normal bir karakter gibi kullanmak istersek \$ ve \. olarak kullanabilirdik. Karakter sınıflarının içinde özel anlam taşıyan - gibi karakterleri karakter sınıfı içinde kullandığımızda yine aynı şekilde \- olarak escape ediyoruz.

Bunun dışında farklı anlama gelen bazı sekanslar da mevcut.

Bunlardan sık kullanılanların bazılarının listesini verelim:

Karakter Anlamı
\n Yeni satır (LF - newline - hex 0A) karakteri
\r CR (carriage return - hex 0D) karakteri
\t Tab (hex 09) karakteri
\xhh Hex kodu hh olan karakter
\d Ondalık sistemde herhangi bir rakam
\D Ondalık sistemde bir rakam değil
\s Herhangi bir boşluk karakteri
\S Boşluk karakteri değil

İşe Yarar Birkaç Örnek


İlk olarak bir preg_replace örneği verelim. PHP regex metin değiştirmeyi destekler ve bunun için preg_replace fonksiyonu kullanılır. Ayrıca aşağıdaki örnekte delimiter olarak / yerine # karakteri kullanılmıştır, farketmeyeceğini belirtmiştik.

<?php
$eski_metin = 'Bugün doğum günüm, 20 yaşıma bastım!';
$yeni_metin= preg_replace("#[0-9]+#", "50", $eski_metin); // Adamı 50 yaşına getirdik
print $yeni_metin;
?>


Kullanıcı adı yalnızca harf rakam ve alt çizgiden oluşsun:

<?php
$username="yere bakan yürek yakan";
if (!preg_match("/^[A-Za-z0-9_]+$/", $username))
    print("Kullanıcı adı yalnızca rakam, harf ve alt çizgiden oluşabilir!");
?>


Girilen E-mail adresi geçerli formattamı:

<?php
$email="sallama@sallamamail.com";
if (!preg_match("/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9\-]+\.[a-zA-Z]{2,4}(\.[a-zA-Z]{2})?$/",$email))
    print("Lütfen geçerli bir e-mail adresi giriniz!");
?>

Bu dökümanın tamamı Tahribat.com için j4x (gVeR) tarafından yazılmıştır. Hiçbir alıntı yapılmamış olup, kod örnekleri de bizzat yazar tarafından uydurulmuştur. (Ç)alıntı yapmayın desem de iplemeyeceksiniz o yüzden demiyorum ama siz yine de yapmayın...

İşin Suyunu Çıkaralım


Buradan sonrası nispeten daha kompleks konuları kapsayacaktır.

Back Referencing (Geriye Referans, Değer Yakalama)


Bir deseni eşleştirirken ya da değiştirirken eski değere başvurmanız gerekebilir. Aşağıdaki örnekte em tagı içerisindeki metni ayıklayıp strong tagları arasına yazıyoruz.

<?php
$metin = "<em>Deneme Metni</em>";
$ayiklanan = preg_replace("/<em>(.+)<\/em>/", "<strong>$1</strong>", $metin);
print $ayiklanan;
?>


Burada parantezlerin kullanımı önemli. Gruplama amaçlı kullanılmıyor. Zaten (.) tek karakter olduğu için gruplamaya ihtiyacımız yok. Burada kullanılış amacı capture yani yakalama. Kısacası eşleşmede tag içinde kalan kısmı paranteze aldık yani yakalanması gerektiğini belirttik. Daha sonra değiştirilecek metin parametresinde $1 şeklinde kullanarak 1. parantezdeki değeri kullanması gerektiğini belirttik ki buna geriye referans diyoruz. Bu şekilde birden fazla parantez kullanabilirsiniz. $1, $2, $3, ... diye istediğiniz parantezdeki değere ulaşabilirsiniz.

Miktar Belirteçlerinin Açgözlülüğü (Greediness)


Örneğin bir HTML tagının içinden değeri ayıklamak istiyorsunuz fakat aynı tagdan birden fazla var. Aşağıdaki örneği inceleyelim:

 

<?php
$metin = "<strong>Deneme1</strong> <strong>Deneme2</strong>";
if (preg_match("/<strong>(.+)<\/strong>/i", $metin, $matches))
    print $matches[1];// $matches[1] = Yakalanan 1. parantezin değeri
?>


Örneği çalıştırdığınızda çıktısı aşağıdaki gibi olacaktır:

Deneme1</strong> <strong>Deneme2


Farkettiyseniz ilk strong ile son strong arasını almış. Yani önüne çıkan ilk </strong> tagını almamış. Peki neden?

Çünkü .+ metnin sonuna kadar olan kısmı alır, sonra 'backtrack' denilen işlemi uygulayarak geri dönmeye başlar. Geri dönüştede ilk tag sondakidir. Bu olaya Greediness (aç gözlülük) denmekte olup; *, + gibi miktar belirleyicileri varsayılan olarak aç gözlüdür. Bunu değiştirmek için .+ kısmını .+? olarak değiştirebiliriz. Böylece ilk strong tagının arasında yazan yani Deneme1 çıktılanır.

<?php
$metin = "<strong>Deneme1</strong> <strong>Deneme2</strong>";
if (preg_match("/<strong>(.+?)<\/strong>/i", $metin, $matches))
    print $matches[1];// $matches[1] = Yakalanan 1. parantezin değeri
?>


Kısacası açgözlülük ? karakteriyle etkisiz hale getirilir.

NOT: U modifierini kullanarak varsayılan olarak miktar belirteçlerinin açgözlü olmamasını sağlayabilirsiniz. Bunu yaparsanız ? karakteri bu sefer açgözlü yapmak için kullanılabilir.

s (PCRE_DOTALL) Modifieri

Bu modifier etkin olduğunda (.) karakteri \n karakteri de dahil olmak üzere tüm karakterler yerine geçer.

Örnek:

<?php


$icerik = '<html>
<head>
<title>Deneme Sayfası</title>

</head>
<body>
<p>Çalınacak Yazı !</p>
</body>
</html>';

if (preg_match("/^.*?<p>(.*?)<\/p>.*?/is", $icerik, $matches))
    print $matches[1];// $matches[1] = Yakalanan 1. parantezin değeri


?>

Yukarıda sayfanın içeriğini desene olduğu gibi geçirmeden noktanın tüm karakterlerin yerine geçmesinden faydalanarak <p> ve </p> tagları arasındaki veriyi çektik. Eğer s modifieri olmasaydı <html> den sonrasını (.) karşılayamayacağından eşleşme sağlanamazdı.


NOT: Dikkatinizi çektiyse yukarıda delimiterdan sonra is yazıyor. Burada 2 modifier kullanmış olduk biri i biri s bunu da belirtmiş olalım.

m (PCRE_MULTILINE) Modifieri


Normalde Regex motoru metni tek bir satır gibi algılar. Yani ^ ve $ özel karakterleri yalnızca en başı ve en sonu belirtir. m modifieri etkin olduğunda ise her satırın başlangıcı ^, her satırın bitişi $ karakterleriyle eşleşir. İlk başlangıç ve son bitiş de buna dahildir.

u (PCRE_UTF8) Modifieri


Bu modifier etkin olduğunda Regex motoru metnin UTF-8 kodlamasında olduğunu varsayar.

Kapanış


Regex özellikle PCRE günümüzde birçok programlama dili ve platform tarafından desteklenmekte olup bilinmesi programcıya büyük faydalar ve rahatlık sağlamaktadır. Bu dökümanda elimden geldiğince basit, orta düzey konuları ve birkaç ileri düzey konuyu açıklamaya çalıştım. Faydası olduysa ne mutlu.

Bu dökümanın tamamı Tahribat.com için j4x (gVeR) tarafından yazılmıştır. Hiçbir alıntı yapılmamış olup, kod örnekleri de bizzat yazar tarafından uydurulmuştur. (Ç)alıntı yapmayın desem de iplemeyeceksiniz o yüzden demiyorum ama siz yine de yapmayın...

Tarih:
Hit: 6509
Yazar: j4x



Yorumlar


Siftahı yapan siz olun
Yorum yapabilmek için üye girişi yapmalısınız.