Bu hassasiyetimi evimdeki Debian GNU/Linux yüklü bilgisayarıma taşımak istedim ve bu tam da 31 Aralık akşamına denk geldi. "Hazır yeni bir yıla giriyoruz, zamanımızı doğru düzgün ölçelim, saniye sektirmeyelim" diye düşünürken (sanki evde nükleer reaktör ya da uzay üssü var, peh!) bir de baktım ki tam da o hassas olduğum zaman benim aleyhime işliyordu çünkü yarım saat içinde yetişmem gereken bir yılbaşı partisi vardı. Ve ben elimdeki abuk sabuk bir HTML dosyasının içinden zaman sunucuların IP adreslerini çekmek istiyordum.
Bir dakika, bir dakika, baştan alalım. Kafalar karışmış olabilir. Eğer sisteminizdeki saat mümkün olabilecek en yüksek hassasiyetle doğru zamanı göstersin istiyorsanız, hassas derken kast ettiğim zaman dilimi değil, zamanın belli bir hassasiyette doğru olması, o zaman imdadınıza NTP yani Network Time Protocol denilen bir protokol ve bununla ilgili yazılımlar yetişir. İşte bu yazılımlardan bir tanesi olan ntpdate yazılımı tam da benim işimi görecek türden küçük, basit bir yazılımdı. Yaptığı iş bir zaman sunucusu bilgisayara bağlanıp NTP protokolünü kullanarak sistem saatini kontrol eden ve gerekli senkronizasyonu, düzeltmeyi gerçekleştirmek olan bu yazılıma komut satırından herhangi bir zaman sunucusunun IP adresini vermek yeterli olacaktı:
$ ntpdate 137.92.140.80
gibi.
apt-get install ntpdate
ile sistemime gerekli yazılımı kurduktan sonra sıra geldi zaman sunucu olarak çalışan bilgisayarların IP adresine ulaşmaya. Biraz araştırınca bunların bir listesine eriştim ve ilgili HTML dosyasını diske kaydettim. Tabii burada küçük bir mesele vardı, benim istediğim aslında yığınla HTML bilgisi değil içinde sadece ve sadece her bir satırında tek bir zaman sunucu IP adresi bulunan bir .txt dosya idi. İşte tüm zamanların en fantastik programlama dillerinden biri olan Perl bu aşamada devreye girdi. Yani elimde acilen işlemem gereken bir HTML dosyası vardı, bunun içinden belli bir kalıba uyan satırları bulup o kalıbı çekmem ve sonucu da başka bir dosyaya yazmam gerekiyordu. Bu arada acilen yetişmem gereken bir yılbaşı partisi olduğunu söylemiş miydim? Çabuk, çabuk! Hemen aradığım kalıbın ne olduğunu düşündüm. Bir IP adresi. Hmm, ancak elimdeki HTML dosyasının içeriğine baktığımda genellikle bu IP adreslerinin parantezler arasında bulunduğunu gördüm. Ayrıca parantezler ile IP adresleri arasında bir miktar boşluk da bulunabiliyordu. Yani aradığım kalıp şöyle bir şeydi: Bir parantez, sonra sıfır ya da daha çok sayıda boşluk, sonra bir IP adresi, sonra gene sıfır ya da daha çok boşluk ve sonra gene bir parantez. Peki IP adresinin kalıbı? Basit: (En az 1, en fazla 3 sayı, sonra bir nokta) * 3 ve hemen ardından da en az 1 en fazla 3 sayı, yani
137.92.140.80
gibi bir şey. Bu kalıbı kafamda netleştirdikten sonra yapmam gereken bunu Perl dilindeki Regular Expression kurallarına uygun olarak söylemekti, o kuralların bir kısmı ise şöyleydi:\s ==> boşluk ([ ] olarak da yazılabilir, bkz. devamı)
* ==> kendinden önce gelen her ne ise ondan sıfır ya da daha çok sayıda
{m, n} ==> kendinden önce gelen her ne ise ondan en az m, en çok n sayıda
{m} ==> kendinden önce gelen her ne ise sadece m sayıda ==> kendinden sonra gelen eğer özel (meta) karakteri normal karakter olarak algıla
[abc] ==> a, b, c veya d karakterlerinden herhangi birini yakala
(abc) ==> gruplandırma, yani "abc"yi tek bir karakter, bir kalıp gibi düşün
Bu kuralları ve elimdeki akıl yürütmeyi göz önünden bulundurarak şöyle bir RE (Regular Expression) kurdum:
\(\s*(\d{1,3}.){3}\d{1,3}\s*\)
(Parantez özel bir karakter yani gruplandırma operatörünün bir parçası olduğu için eğer RE dilinde "parantezi yakala" demek gerekiyorsa bunun için
(
yazmam gerekiyordu, buna dikkat edin.)Şimdi yapmam gereken neydi? Tabii ki ilgili HTML dosyasından yukarıdaki kalıba uyan satırları çekip çıkarmak. Bunun için Perl'e git falanca dosyayı aç diyebilirdim ama zaten Perl denilen güzel dil:
while (<>) {
...
}
yapısını görünce benim komut satırında programa vereceğim herhangi bir dosyayı açıp içini okumaz mıydı ki? Bu durumda yapmam gereken bu yapıyı kullanıp okunan her satırda yukarıdaki RE kalıbına uyan bir şeyler olup olmadığını tespit etmek idi:
while (<>) { if (/\(s*(\d{1,3}.){3}\d{1,3}\s*\)/g) { ... } }
Şimdi gerekli döngüyü ve kontrol yapısını kurduğuma göre ilk yapmam gereken ilgili kalıp bulunur bulunmaz bunu bir değişkene atamak idi. Perl'de bulunan bir kalıbın otomatik olarak
$&
değişkeninde depolandığını biliyordum. Ancak bu yeterli değildi, çünkü tamam yani benim kalıbıma uyanları böylece çekmiş olacaktım ama o zaman elimde (158.12.25.78)
gibi bir şey yani parantezler içinde bir IP adresi olacaktı ve ben bu parantezlerden kurtulmak istiyordum. Yine RE kurallarına göre düşündüm ve kurtulmak istediğim kalıbın şu olduğunu gördüm:[)(]
(Köşeli parantezler içindeki normal parantezlerin sırasını değiştirebilirdim tabii ki, ama o zaman daha doğal gibi görünmekle birlikte benim için biraz kafa karıştırıcı bir görüntü çıkacaktı, bu arada "iyi de parantezler özel karakterler değil miydi, neden onları olduğu gibi yazdın?" diye itiraz edeceklere cevabım: "[ ve ] karakterleri arasında parantez karakterlerinin özel bir anlamı yoktur yani metakarakter muamalesi görmezler.)
Kurtulmak istediğim kalıbın yerine ne koyacaktım? Hiçbir şey! :) İmdadıma Perl'deki SUBSTITUTION yani YERİNE KOYMA operatörü yetişti, kısaca:
s/ARANANKALIP/YERINEKONMASINIISTEDIGIMSEY/
Yani:
s/[)(]//g
(En dipteki
g
, bu işlemi global olarak yani ilgili karakter dizisinin her yerinde yapmak için).IP adresini bir değişkene atadıktan sonra SUBSTITUTION işlemini uygulamanın ve sonucu basmanın yeterli olacağını düşündüm ve şöyle bir şeyler ekleyerek programın gövdesini nerede ise tamamladım:
while (<>) { if (/\(\s*(\d{1,3}.){3}\d{1,3}\s*\)/g) { $ip_address = $&; $ip_address =~ s/[)(]//g; print $ip_address . "\n"; } }
Yapılması gereken bir iki ufak tefek iş kalmıştı: 1) Bu programı Perl'in anlayabileceği şekilde
temp.pl
dosyasının içine yerleştir:temp.pl :
#!/usr/bin/perl -w while (<>) { if (/(\s*(\d{1,3}.){3}\d{1,3}\s*\)/g) { $ip_address = $&; $ip_address =~ s/[)(]//g; print $ip_address . "\n"; } }2) Komut satırından bu programa clock1.html dosyasını girdi olarak verip çıktıyı da TimeServers.txt diye yeni yaratılacak olan bir dosyaya yönlendir:
$ ./temp.pl clock1.html > TimeServers.txt
1.5 haftadır, akşamları 30 dk. harcayarak Perl öğrenmeye çalışan ve daha yolun çok başında olan benim gibi Perl programcısı için fena sayılmaz. Yukarıdaki işlemi gerçekleştirmem yaklaşık 20-25 dakikamı aldı (birkaç deneme yanılma, hata ayıklama vs.) dahil. Yılbaşı partisine de tam zamanında yetiştim ve arkadaşlarımla oynadığım TABU oyununda sıra bana gelip de LAMA ile karşılaşınca yine Perl'den faydalanarak kolayca bu sözcüğü de anlatabildim ;-) Sonra da gelip bu küçük ama anlamlı deneyimi sizinle paylaştım.
Yorumları ile bu yazıyı zenginleştirecek arkadaşlara şimdiden teşekkürler.