İngilizce: Regular Expression (RegEx)
Bu konuyla ilgili Türkçe kaynak araştırdım ancak fazla bulamadım. Şu kaynaktan faydalanarak bilgilerimi pekiştirdim ve şimdi öğrendiklerimi sizlerle paylaşmak istiyorum.
Çeviride “düzenli ifadeler” anlamına gelen bu kavram daha çok bir metin içerisinde kriterlerimize uyan kelime ve olayları saptamak, düzenlemek ve değiştirmek işine yarar. Birçok programlama dilinde geçerli olan bu ifadelere ingilizce “pattern” yani kalıp denmiştir.
Örnek bir kullanımdan bahsedeceksek PHP’de yazılan aşağıdaki kodu inceleyelim:
$degisken = "Ali atta bak.";
echo preg_replace( '~atta~i', 'ata', $degisken );
Kodu çalıştırdığımızda “atta” kelimesini “ata” kelimesine dönüştürerek bize verdiğini göreceğiz. preg_replace kullanımı preg_replace( $kalıp, $değişecek, $değişken ) şeklindedir. Burada regex yani ifademiz ‘~atta~i’ kelimesidir.
Bu kalıp ifadeler özel bir karakterle başlatılır ve aynısıyla bitirilir, sonuna da özellik değişkenleri yazılır. Örneğin yukarıdaki örnekte “i” tek satır arama yapacağımızı belirtir. “m” birden fazla satırda işlem yaparken, “s” ise nokta işaretlerine göre arama yapmamıza yardım eder. Bunlara kalıp ayarı (Pattern Modifiers) diyoruz.
Buraya kadar genel bilgiler verdik. Şimdi kalıplarda yer verdiğimiz sembolleri tanıyalım. Öncelikle aradığımız kelimeyi ~, #, \ gibi karakterlerin arasına yazarak direkt o kelimeyi bulabiliriz. Ayrıca | işareti kullanarak birden fazla olabilecek durum yazmamız mümkün:
$degisken = 'abc';
$degisken = preg_replace( '~a(bc|b)~i', 'Buldum!', $degisken );
echo $degisken;
Yukarıdaki kod çalıştırıldığında değişkenimizin Buldum! olarak değiştiğini göreceğiz. “a(bc|b)” de iki olasılık var. Biri abc olmasıdır, diğeri ab olmasıdır. Her iki durumda da doğru olarak sonuç dönecektir. Ancak değişkenimiz “acd” olsaydı bu kez aynı kaldığını görecektik.
Her zaman aradığımız kelime bilinen bir şey olmayabilir, belli kalıplara göre de arama yapabiliriz. Bunun için bazı özel karakterler kullanırız. Bunlardan 3 tanesini hemen tanıyalım: * (yıldız), + (artı) ve ? (soru işareti).
(*) işareti herhangi bir şey anlamında olup sıfır karaktere de olur der ve sınırsız karaktere kadar gider. (+) ise yıldızdan farklı olarak en az 1 karakter olmasını şart koşar. (?) ise tek bir harf için joker karakter niyetine kullanılır.
Bu üç işaretten önce belli bir kural belirtilir. Kural belirtmek için [] (köşeli parantez) kullanılır. Örneğin [ABC] dersek büyük harflerle A, B ve C olabilir demektir. Diğer örneklere bakalım:
[abc] : a, b, c olabilir.
[0-9] : 0 ile 9 arasındaki tüm sayılar olabilir.
[1-3] : 1 ile 3 arasındaki tüm sayılar olabilir.
[A-Z] : Büyük harfli A’dan Z’ye tüm harfler olabilir.
[a-z] : Küçük harfli A’dan Z’ye tüm harfler olabilir.
[A-Za-z0-9] : Bir harf ya da rakam olabilir.
[-_/$. ] : -, _, /, $, . ve boşluk karakterleri olabilir.
İstersek kaç adet karakter olabileceğini de limitlememiz mümkün. Bunun için { ve } kullanılır. Arasında minimum ve en yüksek karakter sayısı yazılır.
[A-Za-z0-9 ]{1,4} : Bir, iki, üç veya 4 harfli tüm rakam, harf ve boşluk karakterler olabilir.
[1-9]{3} : 100’den 999’a kadar tüm sayılar olabilir.
Şimdiye kadar öğrendiklerimizi iki örnekte görelim.
$degisken = "Murat 1984 yılında Samsun'da doğdu.";
preg_match_all( '/[0-9]*/i', $degisken, $esler );
print_r( $esler );
Yukarıdaki kod çalıştırıldığında 1984’ün seçili olduğunu görebiliriz. Ancak kurala uyan tek kelime 1984 değil, yaklaşık 32 uyan sonuç bulunur (geriye kalanlar boştur).
Ancak yukarıdaki örneği bir de artı (+) kullanarak çalıştırsaydık:
$degisken = "Murat 1984 yılında Samsun'da doğdu.";
preg_match_all( '/[0-9]+/i', $degisken, $esler );
print_r( $esler );
Bu kez TEK SONUÇ çıkacaktı, o da 1984 olurdu. Bu farkın sebebi * karakteri için herhangi bir karakter olmasına gerek yok ancak + simgesi kullanırsak en az 1 adet karakter gerekli.
Şimdi bir de soru işaretini kullanarak bir örnek yapalım.
$degisken = "Murat 1984 yılında Samsun'da doğdu.";
$degisken = preg_replace( '/Mur[A-Za-z]?t/i', 'Mert', $degisken );
echo $degisken;
Bize “Mert 1984 yılında Samsun’da doğdu.” şeklinde dönüş yapacaktır. [koşul]? ile belirttiğimiz yerde “a” karakteri koşulu karşılıyor ve cümle değişiyor. Ancak “a” karakteri orada olmasaydı yani “Murt” olsaydı da Mert olarak değişecekti. Çünkü soru işareti aynı zamanda opsiyonel anlamı katar. Yani olabilir de olmayabilir de.
Başka bir örneğe bakalım:
$degisken = "Programlama demek matematik demek değildir! Mantık demektir!";
$degisken = preg_replace( '~<[A-Za-z][A-Za-z0-9]*>~i', '', $degisken );
echo $degisken;
Çalıştırdığımızda aslında alt yazılı ve kalın olmasını sağlayan U ve B tagları varken silindiklerini göreceğiz.
Burada kural şudur: “<[A-Za-z][A-Za-z0-9]*>“, < ile başlayan, A-Za-z ile devam eden (tek karakter) ve sonrasında A-Za-z0-9 ile devam eden (yıldız süreklilik anlamı katar) ve en sonunda > ile biten bir ifade aranmaktadır. Eğer kuralımız “<[A-Za-z0-9]*>” olsaydı bu kez HTML tagları 0-9 ile de başlayabilecekti. Ancak ilk köşeli parantezde ilk harfteki zorunluluğu [A-Za-z] olarak belirtmiş olduk.
Şimdi başka bir örnekte bir sayı dizisindeki 1000 – 9999 arasındaki sayıları preg_match_all ile bulmasını sağlayalım.
$degisken = '1000 752 1699 10000 25 5860 880800 9800';
preg_match_all( '~\b[1-9][0-9]{3}\b~i', $degisken, $esler );
print_r( $esler );
Kod çalıştırıldığında şu sonucu gösterir: “Array ( [0] => Array ( [0] => 1000 [1] => 1699 [2] => 5860 [3] => 9800 ) ) ” Görüldüğü gibi tam istediğimiz aralıktaki sayıları kendisi buldu. Bunun için kullandığımız “\b[1-9][0-9]{3}\b” kuralında \b bir kelime sınırı anlamına gelmektedir. Örneğin “Ayşe topu at” kelimesinde Ayşe’deki E harfinden sonra \b gelmektedir. Dolayısıyla verdiğimiz dizede boşlukları bölerek arama yapmasını sağlamış olduk. [1-9] ilk sayısı 1 ile 9 arasında olacağını, [0-9]{3} ise sonraki 3 karakterin sayısal bir değeri olacağını belirtir.
Dikkat edildiyse yukarıda yıldız, artı ya da soru işareti kullanmadık. Bunun sebebi kalıpta olması gerekenleri tamamen yazmış olmamız. [1-9] bir karakter, [0-9]{3} üç karakter ediyor. Toplamda 4 karakterli bir şey aradığımız kesin. Karakter sayısı bilindiğine göre +, * gibi sürekli değişkenler ile olabilir ifadesi olan ? karakterlerine gerek yoktur.
Özellikle PHP BOT yazanların sıklıkla yaptığı bir yanlışa da deyinelim. Örneğin em tagları arasındaki kelimeleri almak istiyoruz. Ancak bu durumu sağlayan iki örnek varsa aç gözlü davranıp hepsini birleştirip alır. Örneğin;
“Bu <EM>birinci</EM> test” kelimesinde <herhangibirşey> yapısındaki tüm uyan ifadeleri bulacağız. Bunun için iki farklı kullanımı aynı kodda kullanalım:
$degisken = 'Bu birinci test';
preg_match_all( '~<.+>~i', $degisken, $bulunan1 );
preg_match_all( '~<.+?>~i', $degisken, $bulunan2 );
print_r( $bulunan1 );
print_r( $bulunan2 );
Çıkan sonuçlara bakacak olursak “~<.+>~i” kalıbı kullandığımız 1. komut “<EM>birinci</EM>” şeklinde tek sonuç döndürdü. Ancak soru işareti ekleyerek oluşturduğumuz 2. kalıp “~<.+?>~i” ise soru işareti olasılık anlamı kattığı için fazla açgözlü davranmadı. İki sonuç döndürdü, bunlar da istediğimiz sonuçlar: “<EM>” ve “</EM>” sonuçları.
Son olarak bir de “Ya olmamasını istiyorsak?” diyelim ve şapkalı karakterimizi tanıtalım. Bu karakter olabilecekler listesinin en önüne konulduğunda olmamasını istediklerimiz anlamı katar. Örneğin [0-9] nasıl bir sayı olmasını istediğimizi ifade ediyorsa [^0-9] sayı olmaması anlamına gelir.
$degisken = 'Fetih 1453 filmine gittin mi?';
$degisken = preg_replace( '#[^A-Za-z-_ ]+\b#i', '(sayı)', $degisken );
echo $degisken;
Yukarıda şapkayı koyarak anlattığımız köşeli parantez içindeki ifade bir harf, – (tire), _ (alt çizgi) ve boşluk olmayan ifadeleri bul anlamına geliyor. Her kelime içinde bunu araması için +’dan sonra bir de \b koyduk. Çalıştırdığımızda 1453 olan kısmın (sayı) şeklinde olduğunu saptamış olacağız.
Aslında anlatacak çok şey var ancak giriş için temel bilgiler bunlar. Fırsat bulduğumda (?>(?:>)) tarzı özel ifadelerden faydalanmayı anlatacağım.
Hepinize iyi kodlamalar!