5. Düzenli İfadeler

Şimdi daha ilginç bir yazılım geliştirelim. Bu sefer bir dizgenin verilen bir şablona uyup uymadığını araştıralım:

Bu şablonlar özel anlamları olan bazı karakterler ve karakter birleşimlerinden oluşur:

Tablo 5.1. Düzenli ifade işleçleri ve anlamları
[] aralık belirtimi (Örn, [a-z], a ile z arasındaki bir harfi belirtir.)
\w harf ya da rakam; [0-9A-Za-z] ile aynı
\W ne harf ne de rakam
\s boşluk karakteri; [ \t\n\r\f] ile aynı
\S boşluklar dışında herhangi bir karakter
\d rakam; [0-9] ile aynı
\D rakamlar dışında herhangi bir karakter
\b tersbölü (0x08) (sadece herhangi bir aralık belirtilmişse)
\b kelime içi sınır belirtimi (aralık belirtiminin dışındayken)
\B kelime dışı sınır belirtimi
* öncelediği ifadeyi sıfır ya da daha fazla tekrarlar
+ öncelediği ifadeyi bir ya da daha fazla tekrarlar
{m,n} öncelediği ifadeyi en az m en çok n kez tekrarlar
? öncelediği ifadeyi en fazla bir kere tekrarlar; {0,1} ile aynı
| önündeki veya ardındaki ifade eşleşebilir
() gruplama işleci

Bu ilginç lügat genelde düzenli ifadeler olarak anılır. Ruby'de, Perl'de de olduğu gibi çift tırnak koymak yerine ters bölü işareti kullanılır. Eğer daha önce düzenli ifadelerle karşılaşmadıysanız muhtemelen düzenli hiçbir şey göremeyeceksiniz ancak alışmak için biraz zamana ihtiyacınız olduğunu unutmayın. Düzenli ifadeler, metin dizgeleri üzerinde arama, eşleştirme ve bu gibi diğer işlerle uğraşırken sizi baş ağrısından (ve satırlarca koddan) kurtaran gözle görülür bir güce sahiptir.

Örneğin aşağıdaki tanıma uyan bir dizge aradığımızı farzedelim: "Küçük f harfiyle başlayan, ardından bir büyük harf gelen bundan sonra küçük harf haricinde herhangi bir karakterle devam eden" bir dizge. Eğer deneyimli bir C yazılımcısıysanız muhtemelen şimdiden kafanızca binlerce satır kod yazmıştınız, öyle değil mi? Kabul edin, kendinize güçlükle yardım edebilirsiniz. Ancak Ruby'de dizgeyi sadece şu düzenli ifadeyle sınamanız yeterli olacaktır: /^f[A-Z](^[a-z])*$/.

Açılı ayraçlar arasında onaltılık sayıya ne dersiniz? Hiç sorun değil.

ruby>def chab(s)   # "açılı ayraçlar onaltılık sayı içerir"
    |    (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
    | end
   nil
ruby>  chab "Bu değil."
   false
ruby>  chab "Belki bu? {0x35}"    # kaşlı ayraçlar kullanılmamalıydı
   false
ruby>  chab "Ya da bu? <0x38z7e>"    # onaltılık sayı değil
   false
ruby>  chab "Tamam, bu: <0xfc0004>."
   true

Düzenli ifadeler başlangıçta alengirli gibi gözükse de kısa süre içinde istediğinizi yapabilme konusunda yol katedeceksiniz.

Aşağıda düzenli ifadeleri anlamanıza yarayacak küçük bir yazılım bulunuyor. regx.rb olarak kaydedin ve komut satırına ruby regx.rb yazarak çalıştırın.

#ANSI uçbirim gerektirir!

st = "\033[7m"
en = "\033[m"

while TRUE
  print "str> "
  STDOUT.flush
  str = gets
  break if not str
  str.chop!
  print "pat> "
  STDOUT.flush
  re = gets
  break if not re
  re.chop!
  str.gsub! re, "#{st}\\&#{en}"
  print str, "\n"
end
print "\n"

Yazılım bir tanesi dizge diğeri de düzenli ifade olmak üzere iki girdi alır. Dizge verilen düzenli ifade ile sınanır ve bütün uyuşan sonuçlar listelenir. Şu an ayrıntılara ilgilenmeyin, bu kodun analizini daha sonra yapacağız.

str> foobar
pat> ^fo+
foobar
~~~

Programın çıktısında gördüğünüz ~~~ ile işaretli parça çıktıda artalan ve önalan renkleri yer değiştirmiş olarak çıktılanır.

Bir kaç girdi daha deneyelim.

str> abc012dbcd555
pat> \d
abc012dbcd555
   ~~~    ~~~

Eğer şaşırdıysanız sayfanın başındaki tabloya tekrar göz atabilirsiniz: \d'nin d karakteriyle hiçbir bağlantısı yoktur, sadece bir rakamı eşleştirmekte kullanılır.

Eğer istediğimiz kriterlere uygun birden fazla yol varsa ne olur?

str> foozboozer
pat> f.*z
foozboozer
~~~~~~~~

Düzenli ifadeler olabilecek en uzun dizgeyi döndürdüğü için fooz'un yerine foozbooz eşleştirildi.

Aşağıda iki nokta üstüste işaretiyle sınırlandırılmış bir zaman alanı bulunuyor:

str> Wed Feb  7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb  7 08:58:04  JST 1996
           ~~~~~~~~

"=~" işleci bulduğu dizgenin konumunu döndüren, aksi halde nil döndüren düzenli ifadedir.

ruby> "abcdef" =~ /d/
   3
ruby> "aaaaaa" =~ /d/
   nil