Regular Expressions sözcüklerinin tam çevirisi olan "düzenli deyimler" sözcükleri de hiç hoşuma gitmedi O nedenle bu bölümde anlatacağım kavrama regexp'ler diyeceğim. Böylece ne ingilizce ne de bir başka dilde olacak; aynı bit sözcüğü gibi.
Program yazdığınızı ve bir karakter dizisi içinde "A" harfi olup olmadğını kontrol etme gereksinimini hissettiğinizi düşünün.
a_varmi = index("A", satir) a_varmi = instr(satir, "A")falan gibi bir komut kullanırdınız. Değil mi?
Peki, "A" veya "a" var mı? diye merak etseydiniz?
Ya Harf var mı?" diye merak etseydiniz...
Yazacağınız klasik kod satırları artardı. Oysa "Regular Expression" kullanma olanağı olan bir programlama dili, örneğin Perl kullanıyor olsaydınız
if ( $satir=~ /A/ ) { . . . } if ( $satir=~ /A/i ) { . . . } if ( $satir=~ /[a-z]/i ) { . . . }gibi komut satırları yazabilirdiniz.
Bunların ilki, $satir değişkeninin değeri içinde büyük "A" harfi var mi? diye kontrol ediyor.
İkincisi, $satir içinde büyük ya da küçük "A" harfi var mı? diye kontrol ediyor. (Sondaki "i"
"ignore case" anlamında.)
Üçüncüsü ise $satir içinde "a" ve "z" arasında bir karakter var mi? (büyük küçük
ayırımı yapmaksızın) diye kontrol ediyor.
Regexp'ler bir karakter dizisinin bir kalıba uyup uymadığını kontrol etmek gerektiği zaman kullanışlıdır. Eşitlik kontrolu için de kullanılabilmekle beraber, eşitlik kontrolu için "if a == b" gibi bir yapı daha kullanışlı olacaktır.
Diyelim ki bir karakter dizisinin "0312-2345678" gibi alan kodu sonra bir eksi işareti sonra da rakamlardan oluşan formata uygun olup olmadığını kontrol etmek istiyorsunuz.
Bunun için
if ( $dizi=~ /d*-d*/ )regexp'ini kullanabilirsiniz. Bu regexp'de "d" ve "*" meta-karakterlerdir. Yani dizi içinde "", "d" harfi ve "*" karakterinin bulunup bulunmadığını kontrol etmezler.
d meta-karakteri tüm rakamlara ( [0-9], yani "digit" ) uyar. * meta-karakteri ise "sıfır veya daha fazla tekrarlayan" diye okunur.
Bu durumda "0312-2345678" /d*-d*/ kalıbına uyacaktır. Yani "bir miktar rakam; sonra bir eksi işareti; sonra da gene bir miktar rakam" kalıbına uyacaktir.
Ancak "1-2345678" de bu kalıba uyar; "1-2" de uyar. Hatta "-123" de uyar. Oysa istediğimiz tam olarak bu değildi.
Regexp'i biraz düzeltmek gerekiyor:
if ( $dizi=~ /d{3}-d{7} )"Tam 3 tane rakam, sonra eksi işareti, sonra 7 tane rakam" daha anlamlı bir test yapacaktır.
Bu sefer de "A312-2345678E" dizisi sorun çıkaracaktır, daha doğrusu regexp kalıbına uyacak ve belki programın hatalı çalışmasına yol açacaktır.
Eğer kontrolu "dizinin başında 3 tane rakami sonra eksi işareti, sonunda da 7 tane rakam var mı?" diye yeniden düzenlersek daha iyi olacak gibi...
if ( $dizi=~ /^d{3}-d{7}$/ )Burada "^" "en başta"; "$" ise "en sonda" diye okunur.
Peki dizinin içinde "$" işareti var mı? diye merak ettiğinizde ne olacak?
if ( $dizi=~ /$/ )gibi "" karakteriyle işaretlenen karakterler meta-karakter olarak yorumlanmak yerine, kendileri olarak değerlendirilirler.
Bir başka örnek: Bir nedenle (genetik bilimiyle uğraşıyorsanız gerekecektir) bir dizinin "AAABBCC", "ACC", "AACC" kalıplarına uyup uymadığını kontrol etmeniz gerektiğini düşünün. Bir başka deyişle dizinin içinde bir veya daha fazla "A", sonra bir miktar "B" ve ardından "CC" var mı? diye merak ettiğinizde
/A+B*CC/regexp'i kuıllanılabilir. Bu regexp'i "En az bir tane "A", ardından sıfır veya daha fazla "B", ardından iki tane "C" var mı? diye okuyabilirsiniz.
Şimdi çok kullanılan meta-karakterlere bir göz atalım:
d | Rakamlar |
D | Rakam olmayan karakterler |
w | Bir sözcük içinde yer alabilecek karakterler ( rakamlar, harfler, "_" işareti) |
W | Bir sözcük içinde yer alamayacak karakterler (boşluk, satırbaşı, noktalama işaretleri |
[a-z] | a'dan z'ye küçük harfler |
[A-F] | A'dan F'ye büyük harfler |
[0-9] | Rakamlar |
. | herhangi bir karakter |
[A|X] | "A" veya "X" |
Gene çok kullanılan durum belirtici meta-karakterlerden bazıları:
* | Sıfır veya daha fazla |
+ | Bir veya daha fazla |
? | sıfır veya bir tane |
^ | En başta |
$ | En sonda |
sözcük sınırında (başında ya da sonunda) yer alan |
Örnekler:
$d = " abbbccdaabccdde" ise | |
regexp | Sonuç |
$d =~ /Abc/ | Uymaz! $d içinde hiç "A" yok ki "Abc" olsun! |
$d =~ /Abc/i | Uyar! Sondaki "i" ignore case, yani büyük-küçük harf ayırımı yapılmasın anlamında olduğu için "Abc" ile "abc" eşleşir. |
$d =~ /abc/ | Uyar! Açıklamaya gerek var mı? |
$d =~ /^abc/ | Uymaz! Çünkü "^" meta-karakteri dizinin başında anlamındadır ve "abc" alt dizisi $d'nin başında değildir. |
$d =~ /abc$/ | Uymaz! Çünkü "$" meta-karakteri dizinin sonunda anlamındadır ve "abc" alt dizisi $d'nin sonunda değildir. |
$d =~ /De$/i | Uyar! Dizimizin sonunda "de" var ve büyük-küçük harf ayırımı yapılmayacak! |
$d =~ /^ab*c/ | Uyar, çünkü dizinin başında "a" ve ardından 'sıfır veya daha fazla b' ve ardından "c" var! Bu regexp'teki "*" karakteri hemen solundaki karakter için "sıfır veya daha fazla kez tekrar eden" anlamında bir meta karakterdir. |
$d =~ /aH*bc/ | Hmmm.. Uyar! Çünkü dizide "a" ve ardından sıfır veya daha fazla "H" ve ardında "bc" var! |
$d =~ /aH+bc/ | Uymaz! Çünkü dizide "a" ve ardından en az bir tane "H" ve onun ardında "bc" şeklinde bir düzen yok! Bu regexp'teki "+" karakteri hemen solundaki karakter için "bir veya daha fazla kez tekrar eden" anlamında bir meta-karakterdir. |
$d =~ /a*bc/ | Uymaz! Çünkü dizinin içinde hiç "a" ve ardından gelen "*" yok! Bu örnekte "*" bir meta-karakter olarak değil; basit anlamıyla bir asterisk karakteri olarak kullanıldığı için önündeki "" ile işaretlenmiştir. |
$d =~ /a+bc/ | Uymaz! Çünkü dizinin içinde hiç "a" ve ardından gelen "+" yok! Bu örnekte "+" bir meta-karakter olarak değil; basit anlamıyla bir artı işareti olarak kullanıldığı için önündeki "" ile işaretlenmiştir. |
$d =~ /ac/ | Uymaz! Çünkü dizinin içinde hiç "a" ve ardından gelen "" yok! |
$d =~ /a.*b/ | Uyar! Çünkü dizide "a ve aralarında birşeyler ve sonra b" var! "." (nokta) meta-karakteri herhangi bir karaktere uyar; ardından gelen "*" ile birlikte (yani ".*") birşeyler olarak okunabilir. |
$d =~ /d.*a/ | Uyar! Çünkü "birşey" anlamındaki noktanın ardından gelen "*" meta-karakteri sıfır veya daha fazla herhangi birşey anlamındadır. |
$d =~ /d.+a/ | Uyar! Çünkü "birşey" anlamındaki noktanın ardından gelen meta-karakter bir "+"; yani bir veya daha fazla "herhangi birşey" |
$d =~ /da?/ | Uyar! Çünkü dizide "d" ve ardından bir veya sıfır tane "a" gelen alt dizi var. |
/Abc/i | Uyabilir! $_ özel değişkeninin değerine bağlıdır. Eğer $_ değişkeninin içinde biryerlerde "abc", "aBc", "aBC" gibi bir dizi varsa doğrudur. Dikkat ederseniz $_ değişkeni üzerinde regexp testi yapmak için $_ =~ /Abc/i yazmaya gerek yoktur (ama yazılabilir tabii). |
Kod yazarken bazen de "falanca kalıba uyuyorsa" yerine "falanca kalıba uymuyorsa" mantığı gerekir. O zaman if deyimini
if ( $dizi !~ /abc/ )formunda kullanabilirsiniz.
Regexp'lerle bul-değiştir işlemi de yapılabilir
Örneğin bir dizideki bütün "A" ları "X" ile değiştirmek gerekirse$dizi=~ s/A/X/g;Perl deyimi kullanılabilir. (g: global)
Dizinin başındaki "a"yı "A" ile değiştirmek gerekirse
$dizi=~ s/^a/A/;iş görür.
Şimdi işleri azıcık karıştıralım:
$dizi=~ s/(d+)([A-Z]+)/2-1/g;deyimi bir dizideki "bir miktar rakam ve hemen ardından bir miktar büyük harf" ("12ABCD" gibi) kalıplarını "harfler-rakamlar" a çevirecektir. Yani "123ABC" "ABC-123"e dönüşecektir. Bu işi yapabilmek için geçici belleklere gerek vardır. Yani "bir miktar rakam" kalıbına uyaz dizi parçası belleğe alınacak, ardından gelen "bir miktar harf" de bir başka belleğe alınacak ve bu iki bellek içeriği önce ikincisi gelecek şekilde "-" işaretiyle birleştirilecektir.
Bir kalıba uyan dizi parçasını belleğe atmak için o kalıp parantez içinde yazılır. Parantez çiftleri, kullanılış sırasına göre birinci, ikinci vs numaralı belleklere karşılık gelir. Değiştirme aşamasında bu belleklere 1, 2 gibi meta-karakterlerle erişilebilir.
Perl ile CGI kodu yazılırken çok kullanılan bir yöntemden söz edip bu konuyu kapatmak istiyorum:
Bildiğiniz gibi özel karakterler (noktalama işaretleri, daha doğrusu harf ve rakam olmayan karakterlerin çoğu) CGI parametresi olarak aktarılırken "%hh" şeklinde hex (onaltılık sayı sistemi) kodlarına dönüştürülür. Örneğin "!" işareti "%21" ye dönüştürülerek transfer edilir. CGI parametrelerini karşılayan kod bu şekilde dönüştürülmüş karakterleri geri dönüştürmelidir. Bu işi regexp'le yapmak okuması zor olsa da çok kolay yapılabilir:
$param=~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex(1))/eg;Okunuşu kısaca şöyle:
$param dizisi içinde "%" işaretinden sonra gelebilecek iki hex rakam varsa (sıfırla dokuz arası rakamlar veya A ile F arası harfler) bunu belleğe at ( parantez içinde yazıldığı için) ve bu diziyi ondalık sisteme dönüştürülmek üzere"hex" Perl fonksiyonuna gönder; bu ondalıklı sayısı da "pack" fonksiyonuyla tek karaktere paketle, yani ondalık sayıyı ASCII kodu olarak değerlendirerek karşılığı olan karakteri döndür.
regexp'in sonundaki "e" karakteri, "değiştir" işleminden önce regexp'in değiştirme bloğunda oluşan dizinin önce Perl yorumlayıcısı tarafından yorumlanmasını sağlar (e: execute). "g" parametresi ise bu bul-değiştir işleminin "global", yani $param dizisi içinde olabildiğince tekrarlanmasını sağlar.
Emin olun, "Perl öğreniyoruz" dizisinin yazması en zor bölümüydü bu.
Elinizin altında bir Perl yorumlayıcısı yoksa bu adreste regexp denemeleri yapabilirsiniz.