İçeriğe geçmek için "Enter"a basın

Regular Expressions (Regex) Temel Kullanım

Son güncelleme tarihi 5 Eylül 2022

Regular Expressions hakkında geçmişte yazdığım ufak bir yazı vardı hali hazırda bloğum için kendisini düzenleyip tekrardan paylaşma niyetiyle bu yazıyı hazırlıyorum, kendim için de genel tekrar niteliğinde olacağından eğlenceli bir yazı olacak.

Regular Expressions (Regex) nedir ?

İlk olarak tanımı yapmakta fayda var, Regular Expressions kısaltmasıdır ve dilimize Düzenli İfadeler şeklinde çevirilebilir, yazıda regex olarak kullanacağım.

Ne İşe Yarar bu Regular Expressions (Regex) ?

Bu soruya verilecek cevap başlı başına bir yazı niteliğinde, aslında ne işe yaramaz ki? şeklinde yanıtlanabilecek türden bir şey regex, bu yazıda odaklanmak istediğim iki ana kısım var, ilk kısımda regex temelleri (çoğu kişiye sıkıcı gelebilir), diğer kısmı ise, bir metin içerisinden dilediğimiz özellikteki parçaları ayıklama üzerine olacak.

Eğer sadece kullanım dökümanına geçecekseniz ve teknik inceleme ilginizi çekmiyorsa, buraya tıklayabilirsiniz.

Bit, Byte, KiloByte, Megabyte … bilgisayar bellek birimleri olarak her birimizin diline bir şekilde bulaşmıştır.

Ne alâka regex öğrenmiyor muyduk? diyebilirsiniz, haklısınız da, bir regex işlemi yaptığınızda cpu tarafında ne olduğunu kafanızda iyi kötü şekillendirebilmeniz için aşırı derecede basite indirgeyerek aktaracağım ancak bit ile başlayacağız 🙂

Regular Expressions Başlamadan Önce!

1 bit –> 1 veya 0 değeri alır.
8 bit = 1 byte
1024 byte = 1 KiloByte
1024 KiloByte = 1 MegaByte

şeklinde 2n yani 2 ve üsleri olarak artan ve aslına bakarsanız PC kavramının var olmasını sağlayan temel yapı, QPC kavramı güncel hayatımıza girene kadar da hakim kavram olacak yapı.

Bit anlık olarak yalnızca bir değere sahiptir ve iki adet değer alabilir 1 veya 0 .
Yukarıda da çevrimi gösterildiği üzere 8 bit birleştiğinde 1 byte değere sahip oluyordu.

char Yazılım geliştirmede temel olarak bir karakteri ifade eden kelimedir. Karakter ise bizim bilgisayarımızda klavyemiz üzerinde gördüğümüz her bir harf, her bir simgeyi, her bir rakamı ifade eden şekil. Şimdi belirteceğim kısmı standardizasyonlarda daha da detaylı işleyeceğim için sadece başlık olarak bırakacağım, ASCII nedir? hiç duydunuz mu? “sende bizi çocuk yerine koydun ama” dediğinizi duyar gibiyim, “American Standard Code for Information Interchange” kim bilmez ki!

ASCII standardının var oluşundaki temel ihtiyaç, bilgisayarların yalnızca sayılar (bitler ve üssel ifadeleri, dijital sinyal işleme) ile iletişim kurabilmesinden temelle ortaya çıkmış sayı-karakter çevrim tablosudur. Yüzeysel bir cümle ile tüm bilgisayarlar da bu standardı karşılar. Ekim 1969 yılına ait standart döküman, bu dökümana bakacak olursanız 7 bit ile toplamda 128 adet karaktere kadar sayısal tanımlama yapılabildiğini görürsünüz, Latin karakterler ile birlikte Extended ASCII 8 bit olarak hayatına başladı ve benim örnekleme yapacağım kısım burası, “Ş” karakteri de bu kümede veya ISO-8859-9 diyerek burada bırakayım.

Akılda kalması için ufak bir matematik ile:

20 = 1
21 = 2
22 = 4
23 = 8
24 = 16
25 = 32
26 = 64
27 = 128
28 = 256

değerlerini almakta, referans olarak göz önünde tutalım, diğer bir adım ise boolean cebiri, yani ikilik tabanda sayıların ifade ediliş şekli ve bunlarla mantık işlemlerinin yapılması. 8 bit kullanarak hareket ettiğimizde toplam 256 adet olasılığa sahip olacağız, ifade ediliş şekli ise:

(binary) 00000000 = (decimal) 0
(binary) 11111111 = (decimal) 255

0 – 255 arası toplam 256 olasılık.

Decimal – Binary çevrim için ufak bir araç, doğal olarak PHP ile yazdım ama terminal üzerinden çağırılabilir durumda.

dec2bin.php olarak kaydedip chmod +x dec2bin.php ile çalıştırılabilir yaparsanız bazı işlemler basitleşecektir.

#!/usr/bin/env php
<?php

if(empty($argv[1])) {
    die("Argüman girişi yapılmadı.");
}

if(is_numeric($argv[1]) && $argv[1] >= 0 && $argv[1] <= 255) {
    echo "Sayı doğru aralıkta! \n";
}  
 
$starts = $argv[1];
$ends = $argv[1];

if(!empty($argv[2])) {
    $ends = $argv[2];
}
 
if ($starts > $ends) {
    $temp = $starts;  
    $starts = $ends;
    $ends = $temp;
}
 
for ($i = $starts; $i <= $ends; $i++) {
    $bin = decbin($i);
    echo str_pad($bin, 8, "0", STR_PAD_LEFT) . "\n";
}
 
die;
?>
dec2bin

An itibariyle bitlerin 8li gruplar halinde ifadesini yapabiliyoruz, hadi ozaman ASCII formatına göre harflere bakalım, hangi karakterin karşılığında hangi rakam varmış?

İsteyen olursa ilaveten 2 adet basit uygulama;

dec2char.php

#!/usr/bin/env php
<?php

if(!isset($argv[1])) {
    die("Argüman girişi yapılmadı.");
}

echo chr($argv[1]) . "\n";

die;
?>

char2dec.php

#!/usr/bin/env php
<?php

if(!isset($argv[1])) {
    die("Argüman girişi yapılmadı.");
}

echo ord($argv[1]) . "\n";

die;
?>
char-dec

Ekran görüntüsünde görebileceğiniz üzere, Decimal 65 ( 01000001 ) sayısı ASCII karakterlerde A harfine denk geliyor, ASCII içerisinde 65-90 nümerik aralığı A-Z büyük harfleri barındıran kümedir.

Buraya kadar anlattıklarım tümüyle CPU içerisinde hangi 8bit değer hangi harfe karşılık geliyor anlaşılabilmesi içindi.

Buradan temelle bir kelime üzerinden ufak bir işlem yapacağım, kelimemiz “kelime” olsun, ingilizce karakter içermesine dikkat edersek fazla sorun yaşamayız, 64 bit tam ölçekli bir sistemde 16bit UTF-8 karakter işlemeyeceğiz sonuçta.

yasin@uxn-workstation:~/bin$ char2dec.php k
107
yasin@uxn-workstation:~/bin$ char2dec.php e
101
yasin@uxn-workstation:~/bin$ char2dec.php l
108
yasin@uxn-workstation:~/bin$ char2dec.php i
105
yasin@uxn-workstation:~/bin$ char2dec.php m
109
yasin@uxn-workstation:~/bin$ char2dec.php e
101

Ardından bu sayıları binary formata çevirelim,

k = 107 ( 01101011 )
e = 101 ( 01100101 )
l = 108 ( 01101100 )
i = 105 ( 01101001 )
m = 109 ( 01101101 )
e = 101 ( 01100101 )

başka bir kavram olan Most Significant Bit ve Least Significant Bit kavramlarına detaylıca girmeden, soldan sağa okuma düzeni olan MSB kullanacağım.

011010110110010101101100011010010110110101100101

Kelimemiz toplamda 6 byte ve 48 bit binary formda bir sayı elde ettik, yaptığım şey 8bit formattaki sayıları soldan sağa doğru birbirine eklemek oldu. Tek seferde 8bit işleyen bir CPU her döngüsünde bir karakter okuyacak şeklinde bir senaryo belirleyelim, 8bit 16Mhz Atmel 328p-pu saniyede 16Milyon karakter işleyebiliyor mesela.

1. [01101011] 2. [01100101] 3. [01101100] 4. [01101001] 5. [01101101] 6. [01100101]

Her adımda 1byte işleme alınıyor bu sebepten harf ıskalama ihtimalimiz yok, yeni bir kavram bitshifting, aşırı derece basit olarak 1byte okunduktan sonra 2.byte okumak için ilk küme kaydırılır, basitçe buna bitshifting denir.
Diğer bir kavram bitmask, maskeleme işlemi diyelim, k – 107 ( 01101011 ) sayısından çıkarttığımız herhangi bir sayı sonucu sıfır olduğunda gördüğümüz şey k karakteridir diyebilir miyiz? veya tersinir mantıkta sonucu sıfır olmayanlar k karakteri haricinde herhangi bir karakter diyebilir miyiz? veya decimal 65 – 90 aralığındaki, maskeleyerek tespit ettiğimiz herhangi bir sayı değeri için Alfanümerik karakter kümesindedir diyebilir miyiz?

Regular Expressions kavramı;

teknik olarak bu altyapı ile işlemekte, kısaca bildiğimiz toplama çıkartma, kişisel bilgisayarım o kadar da yeni değil intel ark sayfasına göre 2013 yılında üretilmiş, 5GT/s data aktarım hızına sahip 64bit bir işlemciye sahip, yaklaşık 7.4GB/s data bu CPU üzerinden geçebiliyor. Bu hesaplama yanlış, çok fazla değişkene bağlı ve bu kadar basite indirgenmemeli normalde, ama yapmak istediğim örnekleme için yeterli olacaktır.

Kısa ve özetle 8 x 8 x 109 bilgisayarımın işlemcisinden bir saniyede geçebilecek karakter sayısını işaret ediyor. 64 milyar adet karakter!

regex yazılımı yavaşlatır diyen arkadaşlara saygılarımla, kullanmak zor derseniz daha mantıklı olur.

( With respect to those who say regex slows down the software, it would make more sense if you say it is difficult to use. )

Bu konularda bilgim aşırı derecede yıpranmış durumda bu sebepten daha detaylı şekilde işlemek için derinlemesine öğrenmem lalzım, eğer bu işlemlerin standardizasyonu veya daha detaylı öğrenimi gerekirse şahsen ilk başlayacağım döküman PCRE – Perl-compatible regular expressions olurdu, bu kütüphaneler içerisinde hali hazırda tüm temel sistematik işlenmiş durumda.

Kısacası regex ve string arama işlevleri (standardizasyonu) şu anda teknik bilgimi oldukça aşan akademik seviyede konular, yukarıda niçin akademik bir konu olduğunu, bağıntılı alanların derinliğini özet olarak görebildiğinize eminim.

Gelelim asıl konumuza, hadi regex kullanalım adamlar zaten standardını da, uygulamasını da geliştirmiş!

Hadi Regex Kullanmaya Başlayalım,

Regular-Expressions

Regex : Bir “string” üzerinde, işaretçiler kullanarak belirtilen “düzende” “eşleşmeleri” döndüren sistematiğe verilen isim.

Şeklinde bir tanım yapacak olursam yanlış olmaz sanırım. Tanımda geçen ifadelerden;

String : arama yapacağımız yazı, metin ve içerdiği karakterler (satır sonu, boşluk ve tab dahi birer karakterdir).
İşaretçi : Düzen (pattern) oluştururken kullandığımız karakterler,
Düzen (pattern) : string içerisinde ne aradığımızı, işaretçiler kullanarak belirttiğimiz(kedinin klavyede tepinmesi olayı) düzenli işaretler,
Eşleşmeler : düzen ile string içerisindeki eşleşmeler, gruplanmış da olabilir, tekil de.

Doğrudan örnekler üzerinden gidelim. Daha kolay anlaşılacaktır.

Perl Compatible Regular Expressions(PCRE) standardına uygun olarak işlem yapan uygulamalarda, aşağıda örneklemesi yapılacak işlemler aynen çalışacaktır( Perl, Php, C, C++, Bash ). Yazılan “düzen” diğer standartlar için de benzer şekilde kullanılabilir. Bu yazımda PCRE üzerinden gidiyorum.

  1. Öncelikle bir stringe ihtiyacımız var
  2. Bu string içerisinde hangi kıstaslar kullanılarak arama yapılacak bunları belirleyeceğiz(Flags(Bayraklar)),
  3. Bu string içerisinde hangi düzende arama yapılacak bu kalıbı oluşturacağız.(Pattern(Düzen))

Uygulamalarımı https://regex101.com sitesi üzerinden yapacağım, hazırladıkları site gerçekten güzel.

Örnek yazımız:

Lorem Ipsum Nedir?
Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.
Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır.
1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur.
私の名はヤシンです。
lorem-ipsum

Bu yazı(bundan sonra string olarak belirteceğim) içerisinde hangi kıstaslarda arama yapılacak sorusunun cevabı “bayraklar(flags)” tarafından verilir, en sık kullanılanların anlamları,

  • [g] global bayrağı, ilk eşleşmeden sonra devam et!
  • [m] multiline bayrağı, string sonuna kadar git ilk satır sonunda durma!
  • [i] insensitive bayrağı, büyük küçük harf duyarsız davran!
  • [x] extended bayrağı, beyaz boşlukları(white space) görmezden gel!
  • [u] unicode bayrağı, şçöğİ私の名は gibi karakterleri de içer!

Yani “/…/gmui” bayrakları ile yapmak istediğimiz bir çok şeyi yapabiliriz.

Gelelim düzene(pattern) o kadarda korkulacak bir şey değil aslında, sadece elin alışması gerek, ve kurallara uymak gerek. Düzeni her zaman için /…/gmui sınırlayıcı (delimiter) karakter (bizim durumumuzda “/“) aralığına yazarız, bayraklar ise sınırlayıcı kapanışının hemen bitişiğine yazılır.

Tekil Karakter Seçimi

[] köşeli parantez içerisine yazılan her karakter için string içerisindeki eşleşmeleri listeler.

[a]Örnek string içerisinde 56 adet “a” karakteri mevcutmuş.

tekil-eşleşmeler

[asd]Örnek string düzeni bize string içerisinde her a,s,d karakterleri için eşleşmeleri döndürecektir. a, s veya d karakterlerinden toplamda 98 adet varmış.

Satır başından itibaren seç

^ karakteri ile başlayan düzen bize satır başından itibaren seçimin başlayacağını belirtir.

Dikkat! ^ ve [^…] birbirinden oldukça farklı şeyler, biraz aşağıda işaretlenen haricindekileri seçmek kısmında açıklanacak.

^Lveya ^l düzeni bize l veya L ( i bayrağından dolayı ) karakteri ile başlayan eşleşmeleri döndürecektir, eğer insensitive (i) bayrağını kaldırırsak karakter büyük veya küçük hangisi düzende belirtildiyse onu döndürecektir.

satır-başı-seçimi

Aralık (range) Seçimi

[a-z] [A-Z] [0-9] şeklinde köşeli parantez içerisinde aralık belirtilir.

Dikkat :
i (insensitive) bayrağı varsa [a-z] ve [A-Z] aynı şeydir.
[a-z] içerisinde unicode karakterler yer almaz, ASCII standardındaki a-z aralığındaki her bir harfi işaretler.
[0-9] her bir rakamı işaretler.

İşaretlenen Haricindekileri Seçmek

[^…] köşeli parantez içerisinde ^ simgesi ile belirtilen karakterler veya aralık haricindekileri seçer

[^a-z] düzeni, metin içerisindeki a-z, A-Z aralığı dışında kalan boşluk ve tab (whitespace) dahil tüm karakterleri işaretler.

a-z haricindekiler

Herhangi bir karakteri seçmek

Nasıl yani? herhangi bir karakter derken tam olarak anlatamamış olabilirim, metin içerisinde herhangi bir karakteri işaret etme kıstasını uygulayan düzen, dersem ve yanına da a.a dersem metin içerisinde ada, ara, a a, asa yani “a” ve “a” arasında ilave herhangi bir karakter varsa üç karakteri birden seçme işlemi yapar.

Dikkat! . ve [.] birbirinden farklı şeyler, ilki herhangi bir karakter manası taşırken diğeri . (nokta) karakterinin kendisini ifade eder.

a.a a, a dahil olmak üzere arasındaki diğer karakter ile birlikte üç karakterlik seçme işlemi yapar.

a.a
nokta

Whitespace (Boşluk) işaretleme

\s düzeni her bir beyaz boşluğu işaretle demek

Metin içerisindeki her bir boşluk ve tab karakterlerini işaretleyecektir.

whitespaces

Whitespace (Boşluk) Haricindekileri işaretleme

\S düzeni boşluk ve tab haricindeki tüm karakterleri işaretleyecektir.

boşluk haricindekiler

Rakam İşaretleme

\d düzeni metin içerisindeki her bir rakamı ayrıca işaretleyecektir.

rakam

Rakam Olmayan Her Bir Karakteri İşaretleme

\D düzeni, metin içerisinde rakam olmayan her karakteri işaretler.

Kelime Oluşturmada Kullanılabilen Karakterleri İşaretleme

Dikkat! unicode (u) bayrağımız olduğu için, “ığüşİöç…” gibi karakterleri de işaretleyecektir, bu bayrağı kaldırırsak yalnız ASCII karakter kümesini kullanacaktır.

\w düzeni, bir kelime oluşturmak için kullandığımız harf, sayı gibi karakterleri işaretler.

Kelime Oluşturmada Kullanılabilen Karakterlerin Haricindekileri İşaretleme

\W düzeni, bir kelime oluşturmak için kullandığımız harf, sayı gibi karakterler haricinde kalan karakterleri işaretler.

kelime olmayan

Kelime Sınırlarını Seçmek

Bir kelimenin başlangıcı ve sonu vardır, bu sınırlar bir önceki düzende belirttiğimiz kelimeyi oluşturan karakterlerin başlangıcı ve sonunu işaret eder.

\b Kelime Sınırı Seçimi

kelime sınırı

görselde de görüleceği üzere kelimelerin başlangıç ve bitiş alanlarını işaretledi. Mesela a harfi ile başlayan veya biten kelimeleri seçelim.

a harfi ile

a harfi ile biten kelimeleri de seçelim mi?

a harfi ile biten

\B Kelime Sınırlarının Dışında Kalan Karakterlerden Seçim Yapmak

\b düzeni ile kelime sınırlarından seçim yaptık, kelime sınırları haricinde kalan karakter kümesinden seçim yapmak için \B düzenini kullanacağız. Metin içerisinde kullanılan kelimelerin hangisinin içerisinde rakam mevcut bakalım.

rakam var

Veya metin içerisindeki kelimelerin hangilerinin içerisinde psu karakterleri yan yana gelmiş görelim. Düzenimiz \Bpsu

Dikkat! kelime sınırı başlangıcı veya sonu olan karakterler seçim dışında kalacağından ips şeklinde arama çalışmayacaktır.

Veya İşlemi İle İşaretleme Yapmak

|simgesi veya manasına gelen düzendir. Yukarıdaki kelime sısnırı örneğinden yola çıkarak, a harfi ile başlayan veya a karakteri ile biten kelimelerdeki a karakterlerini işaretle diyelim. Düzenimiz \ba|a\b

başı sonu

Karakterleri Gruplayarak Seçme İşlemi

Nedir bu gruplama işlemi? gündelik hayattan örneklemek gerekirse sabah kalktınız ve kendinize geldikten sonra kahvaltı sofrasına oturdunuz, sofrada bardaklar, tabaklar, çatallar, kaşıklar … şeklinde odaklanarak baktığınızda her nesne için grup oluşturabiliyorsunuz. Örnekleme genişletilebilir, detaylandırılabilir ama özü anlatabildim umarım. Benzer şekilde bir metin içerisinde karakter kümelerini de regex kullanarak gruplayabiliriz, hadi öğrenelim.

() parantez kullanılarak oluşturulan düzen gruplanmış şekilde işaretlenir.

Mesela a karakteri içeren veya rakam içeren her bir karakteri işaretleyelim. Düzenimiz (a)|(\d)

gruplama

Yukarıdaki görselleri inceleyecek olursanız şimdiye kadar tek bir renk ile işaretleme yapılmış, bu örneğin görseline bakacak olursanız her bir grubun kendine ait rengi var. Bu sitenin sağladığı özellik ancak programlama ile bakacak olursak bu tip regex işlemi sonrası genellikle Array tipinde data dönüşü sağlanacaktır. Bu yazının sonunda birkaç tane programlama dili ile örneklemeler yapacağım bu konuyu orada tekrar göreceğiz.

Benzer şekilde metnimizi 2, 3 ve 4 karakterlik gruplara bölelim. Kendiniz denemek ister misiniz? ufaktan alıştırma yapmak iyi gelecektir.

“Ben bir kerede 10.000 tekme çalışandan korkmam ama bir tekmeyi 10.000 kere çalışandan korkarım.”

Bruce Lee

Görseli gizledim ve Buradan erişebilirsiniz. Görselde göreceğiniz gibi metnin başından itibaren 2, 3 ve 4 karakterlik gruplar halinde metni işaretledik.

Kendinden önceki karakter, 0 veya 1 adet ise.

? merak etmeyin soru cümlesini yazmayı unutmadım, bir regex ifadesi daha ? (soru işareti)

Bu ifade kullanıldığında kendisinden önce belirtilen karakterden 0 veya 1 adet var mı diye kontrol eder ve işaretler. En basit örneği, nedenini araştırmak dahi istemediğim, ne dertleri vardı da böyle bir dosya uzantısı oluştu dediğim tahmin ettiğiniz jpg veya jpeg dosya uzantısı, dosya formatı aynı olmakala birlikte sadece uzantı farklı ve mime: image/jpeg ezici biçimde genel kullanım şekli.

Konu dışına çıkmadan: jpe?g düzeni metin içerisinde jpg veya jpeg karakter kümesi var mı kontrol eder. Tanımda da belirtildiği üzere kendisinden önceki e karakterinden olsa da olmasa da işaretleme işlemi yapıyor. Kendi örnek metnimizde ise, as?a düzeni ve 1\d?\d?0 düzeni gözüme çarptı. Gruplayarak işaretleyelim.

asa 1500 grup

Kendinden önceki karakter sıfır veya sonsuz defa olsa bile işaretle .

* (asterisk) veya yıldız ya da çarpım sembolü artık ne demek isterseniz, sıfır veya sonsuz adet seçim için kullanılan işaretçi.

En fazla kullanacağımız işaretçilerden birtanesi olmakla birlikte, kendinden önceki karakterden 0 veya sonsuz adet olanı işaretler. Hatırlayacağımız üzere . (nokta) işaretçisi düzen olarak kullanıldığında “herhangi bir karakter” manasına gelmekteydi. .* şeklinde kullanacağımız düzen tüm metni tek parça halinde seçecektir. Bu şekilde eğlenceli olmaz o yüzden metnimize bakarak a karakterleri ve rakamları ayrı gruplar halinde seçelim.

a ve rakamlar

Metnimize ufak bir satır ekliyorum, 私の名はヤシンです。bu yazıyı hazırlarken ihtiyacım olduğunu farkettiğimden siz alıştırma yaparken en başından sahip olacaksınız.

Asterisk kullanarak aralık seçme işlemi de yapabiliyoruz, mesela 私 ile başlayan 。karakteri ile biten metni işaretleyelim. 私(.*)。 düzeni bu işi görecektir. Ancak farkettiğiniz üzere gruplama içerisinde işaretçi kullanıldığından 2 grup şeklinde sonuç görüntülenecek.

Düzenimizi 私.*。 şeklinde oluşturursak tekil şekilde tüm cümleyi seçecektir, bu arada merak etmeseniz de söyleyeyim, Japonca “Benim adım Yasindir.” yazıyor orada.

Kendinden önceki karakter bir veya sonsuz defa olsa bile işaretle

+ (artı) İşaretçisi, kendisinden önce gelen karakterden 1 veya sonsuz adete kadar varsa seçim işlemi yapar..

* İle birlikte + işaretçisi en fazla kullanacağımız işaretçiler olacak desem yalan olmaz. Ufak bir tekrar yapalım daha kalıcı olacaktır.

  • ? kendisinden önceki karakterden 0 veya 1 adet varsa
  • * kendisinden önceki karakterden 0 veya sonsuz adete kadar
  • + kendisinden önceki karakterden 1 veya sonsuz adete kadar

işaretlemeyi sağlar.

Satır sonunu işaretle.

$(dolar karakteri) işaretçisi satır sonu manasına gelmektedir.

Örnekleyecek olursak metnimiz karakterlerden oluşuyor ancak karakterler kelimeleri, onlar da cümleleri onları da ayrıca ifade etmek istersek satırları oluşturuyor. Satır sonu aslında başlı başına bir karakter olup [ unix line ending “\n” ASCII 0x0a (10) – LF ] [ windows carriage return & line ending “\r\n” ASCII 0x0d (13) / ASCII 0x0a (10) CR/LF ] işaretlenebilmektedir.

Örnek olarak metnimizdeki her bir satırı ^.*$ düzeni ile seçelim. Satır sonu işaretçisini konunun en altına ekleme gereksinimi tümüyle bu örnekten kaynaklı. Metnimizde 5 adet satır varmış.

Kendinden önceki karakter kaç adete kadar belirtilirse işaretle.

{ } Süslü parantez işaretçisi, kendinden önceki karakterin/grubun kaç adet veya kaç adete kadar seçilerek işaretleneceğini belirler.

Örneklerle ilerlersek daha kolay olacaktır.

  • \d{1} (adet seçimi) düzeni, metin içerisindeki rakamları tekil şekilde seçmeyi sağlar,
  • \d{1,} (sonsuz aralık seçimi) düzeni, metin içerisindeki rakamları, bir veya sonsuz adete kadar seçer,
  • \d{1,2} (sınırlı aralık seçimi) düzeni, metin içerisindeki rakamları, iki adete kadar seçer,
süslü-parantez

Genel regex kullanımı için gereken bilgi buraya kadar, yukarıda tanıtılan işaretçi ve düzenler ile ne kadar uygulama yaparsanız o kadar gelişeceğinize eminim.

Yazının bu kısmına kadar okuduysanız tebrikler ve canı gönülden teşekkürler. Burada işlemediğim ancak daha sonra yazısını yazmayı düşündüğüm ileri seviye regex kullanımı ve örnekleri yazısını da bu sayfaya link olarak ekleyeceğim. Hadi biraz da programa yazalım!

Birkaç programlama dilinden örnekler ile Regular Expressions.

Önceliği C diline ayırıyorum, olabildiğince basit gideceğim.

Bu uygulama için kullanacağım araç seti;

  • GNU / Linux Debian 11 (bullseye)
  • gcc 10.2

Uygulamamızın yapmasını istediğim tek özellik var, kendisine argüman olarak verilen pattern metin.txt dosyası üzerinde çalıştırılsın ve sonuçları ekrana yazsın. C geliştirme uzmanı değilim o sebepten olabildiğince basit tutmaya çalıştım regex.h pcre uyumlu değil bu sebepten $ satır sonumuz yok ve \d yerine [0-9], satır seçmek için \b.*\b gibi alternatifler kullanılmalı.

Metin editörümüzü açıp regex-test.c isminde bir dosya oluşturalım ve içerisine aşağıdaki kodu ekleyelim.

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <regex.h>
#include <locale.h>

int 
main (int argc, char *argv[])
{
	// File pointer
	FILE * fp;
	
	// İşlenecekler
	char line[1000] = "";
	char string[10000] = "";
	char pattern[1000] = "";
	
	// Regex için
	size_t maxMatches = 10000;
	size_t maxGroups = 3;
	regex_t regexCompiled;
	regmatch_t groupArray[maxGroups];
	unsigned int m;
	char * cursor;
	
	// terminalde unicode karakterleri gösterebilmek için.
	setlocale(LC_ALL, ""); /* Use system locale instead of default "C" */
	
	if ( argc > 1 )
	{
		strcat(pattern, argv[1]);
	}	

	printf("Kullanılan pattern : %s\n", pattern);
	
	fp = fopen("metin.txt","r");
	
	while (fgets(line, sizeof(line), fp) != NULL)
	{
		strcat(string, line);
	}
	
	printf("Kullanılacak Metin :\n%s\n", string);
	printf("----------------------------------------\n\n");
	
	if (argv[1] == NULL)
		return 0;
	
	if (regcomp(&regexCompiled, pattern, REG_EXTENDED|REG_ICASE|REG_NEWLINE|REG_EESCAPE))
    {
      printf("Could not compile regular expression.\n");
      return 1;
    }
    
	m = 0;
	cursor = string;
	
	for (m = 0; m < maxMatches; m ++)
	{
	  if (regexec(&regexCompiled, cursor, maxGroups, groupArray, 0))
		break;  // No more matches

	  unsigned int g = 0;
	  unsigned int offset = 0;
	  
	  for (g = 0; g < maxGroups; g++)
		{
			if (groupArray[g].rm_so == (size_t)-1)
			break;  // No more groups
			
			if (g == 0)
				offset = groupArray[g].rm_eo;
				
			char cursorCopy[strlen(cursor) + 1];
			strcpy(cursorCopy, cursor);
			cursorCopy[groupArray[g].rm_eo] = 0;
			
			printf(
			"Match %u, Group %u: [%2u-%2u]: %s\n", 
				m, g, groupArray[g].rm_so, groupArray[g].rm_eo,
				cursorCopy + groupArray[g].rm_so
			);
			
		}
	  cursor += offset;
	}

	regfree(&regexCompiled);
    
	fclose(fp);
	
	return 0;
}

gcc yüklü sistemde gcc regex.c -o regex-test komutuyla uygulamamızı derledikten sonra, önceki örneklerden birini yapalım.

yasin@uxn-workstation:~/test/c$ gcc regex.c -o regex-test
yasin@uxn-workstation:~/test/c$ ./regex-test "(\ba)|(\Bpsu)"
Kullanılan pattern : (\ba)|(\Bpsu)
Kullanılacak Metin :
Lorem Ipsum Nedir?
Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.
Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır.
1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur.
私の名はヤシンです。

----------------------------------------

Match 0, Group 0: [ 7-10]: psu
Match 1, Group 0: [93-96]: psu
Match 2, Group 0: [ 3- 4]: a
Match 2, Group 1: [ 3- 4]: a
Match 3, Group 0: [268-269]: a
Match 3, Group 1: [268-269]: a
Match 4, Group 0: [88-91]: psu
Match 5, Group 0: [86-87]: A
Match 5, Group 1: [86-87]: A
yasin@uxn-workstation:~/test/c$ 

C mücadelesini kazandım 🙂 Sırada PHP var.

PHP ile Regular Expressions

regex-test.php isminde bir dosya hazırlayıp ilk satıra sha-bang ekleyip içeriğine aşağıdaki kodu ekleyip chmod +x dedikten sonra php uygulamamızı da çalıştırabiliriz.

#!/usr/bin/env php
<?php

$pattern = isset($argv[1])?$argv[1]:null;
$string = file_get_contents("metin.txt");

echo "Kullanılan pattern : " . $pattern . "\n\n";
echo "Kullanılan string : " . $string . "\n";
echo "---------------------------------------.\n\n";

if (empty($pattern))
	die;

$pattern = "/" . $pattern . "/mui";

preg_match_all($pattern, $string, $matches);

print_r($matches);
?>

PHP pcre uyumlu olduğundan ^.*$ düzenimiz sorunsuz şekilde çalıştı. Yukarıdaki örneklerin tümünü PHP ile aynen uygulayabilirsiniz demek oluyor.

yasin@uxn-workstation:~/test/php$ ./regex-test.php '^.*$'
Kullanılan pattern : ^.*$

Kullanılan string : Lorem Ipsum Nedir?
Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.
Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır.
1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur.
私の名はヤシンです。

---------------------------------------.

Array
(
    [0] => Array
        (
            [0] => Lorem Ipsum Nedir?
            [1] => Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.
            [2] => Lorem Ipsum, adı bilinmeyen bir matbaacının bir hurufat numune kitabı oluşturmak üzere bir yazı galerisini alarak karıştırdığı 1500'lerden beri endüstri standardı sahte metinler olarak kullanılmıştır. Beşyüz yıl boyunca varlığını sürdürmekle kalmamış, aynı zamanda pek değişmeden elektronik dizgiye de sıçramıştır.
            [3] => 1960'larda Lorem Ipsum pasajları da içeren Letraset yapraklarının yayınlanması ile ve yakın zamanda Aldus PageMaker gibi Lorem Ipsum sürümleri içeren masaüstü yayıncılık yazılımları ile popüler olmuştur.
            [4] => 私の名はヤシンです。
        )

)
yasin@uxn-workstation:~/test/php$ 

Keyifle hazırladığım bir yazının daha sonu, ana başlıklara birdaha göz atacak olursak eğer:

  • Ne İşe Yarar Bu Regex dedik ve olayın dinamiğine yüzeysel olarak dokunduk,
  • Kullanmaya Başlayalım dedik ve hızlı bir giriş ile Temel seviye regex kullanımına baktık, işaretçi, bayrak, string, sınırlayıcı gibi kavramlardan sonra regex101 ile bu site arasında gidip geldik sürekli.
  • Program Yazalım dedik, C ve PHP ile nasıl kullanıldığını gördük, diğer programlama dillerinde de benzer şekilde içerdiği fonksiyon ile benzer uygulamalar yapılabilir, mesela javascript (match, matchAll, test) gibi yerleşik fonksiyonları bize bunu sağlıyor.

Bonus:

Regex ile ilgili basit örnekleme ve uygulamamızı da yaptık, bir adım sonrasında, gerçek hayatta nasıl bir alanda kullanılabileceği hakkında ufak bir örnekleme ile olayı pekiştirelim ve diğer konuları ileri seviye regex kullanımı yazımıza bırakalım.

Gerçek hayat örneklemem Apache loglarının okunmasını kolaylaştıracak bir uygulama üzerine olacak, baştan uyarayım aşağıda paylaşacağım pattern web sunucumda çalışır durumdaki bir Apache Web server access log üzerinde çalışacak şekilde düzenlendi, Web Server Kategorisinde detaylıca anlatılacak olan log yapılandırmasında çalışacak bir düzen ve yapılandırma değişken olduğu için pattern kopyala yapıştır şeklinde çalışmayabilir!

Aşağıdaki kod web sitemize ait access.log ve rotated access.log.gz dosyalarımızın her birini açıp içerisinde arama yapmama müsaade ediyor. Uygulamayı yorumlayabilmek için az biraz bilgi sahibi olunması gereken başlıklar, Linux terminal, bash, apache logs, php, ls, cat, zcat, grep, regex, ve yazılım geliştirmek için gereken ihtiyaç analizi.

prettify.php

#!/usr/bin/env php
<?php

$showLogs[] = "today";
$log = array();

$help[] = "---------------------------------------------";
$help[] = "Help: ./script-name <time> \t=> ./prettify.php [ today | yesterday | all ]";
$help[] = "Arguments can be more then one \t=> ./prettify.php today yesterday";
$help[] = "---------------------------------------------\n\n";
echo implode("\n", $help);

if ( isset($argv[1]) && count($argv) > 1 ) {
	for ($i=1; $i<count($argv);$i++)
		$showLogs[$i-1] = $argv[$i];
}

//~ print_r($showLogs);

$todaysFile = date('Ymd') . '-access.log';
$yesterdaysFile = date('Ymd',strtotime('yesterday')) . '-access.log';

function getLog($file) {
	return file_get_contents($file);
}

if (in_array("today", $showLogs)) {
	$log[$todaysFile] = getLog($todaysFile);
}

if (in_array("yesterday", $showLogs)) {
	$log[$yesterdaysFile] = getLog($yesterdaysFile);
}

if (in_array("all", $showLogs)) {
	
	$log[$todaysFile] = getLog($todaysFile);
	$log[$yesterdaysFile] = getLog($yesterdaysFile);
	
	exec('ls -rv | grep -P "^20.*.gz"', $fileListRotated);
	foreach ($fileListRotated as $val) {
		exec("zcat $val", $rotatedLog);
		$rotatedLog = implode("\n", $rotatedLog);
		$log[$val] = $rotatedLog;
		break;
	}
	
	//~ print_r($fileListRotated);
}

//~ print_r(array_keys($log));


$pattern = '/^([\d.\w:]+)[\s-]+\[([\d\w\/:]+)[\s\+\d]+\]\s+"(\w+)\s+([\/\w\d\-\_.?=&:]+)\s+([\w\/\d.]+)"\s+(\d+)\s+(\d+)\s+"([\/\w\d\-\_.?=&:]+)"\s+"(.+)"$/mui';

foreach ($log as $key => $val) {
	
	preg_match_all($pattern, $val, $matches);
	for($i=0; $i<(count($matches[1])); $i++) {
		$ip[] = $matches[1][$i];
		$date[] = $matches[2][$i];
		$method[] = $matches[3][$i];
		$query[] = $matches[4][$i];
		$httpVer[] = $matches[5][$i];
		$statusCode[] = $matches[6][$i];
		$port[] = $matches[7][$i];
		$referer[] = $matches[8][$i];
		$userAgent[] = $matches[9][$i];

echo "
IP \t: \t$ip[$i]
DATE \t: \t$date[$i]
METHOD \t: \t$method[$i]
QUERY \t: \t$query[$i]
HTTP V \t: \t$httpVer[$i]
STATUS \t: \t$statusCode[$i]
PORT \t: \t$port[$i]
REF \t: \t$referer[$i]
UAGENT \t: \t$userAgent[$i]
\n";
	}
}
?>

Yukarıdaki uygulama; belirtilen access.log dosyasını/dosyalarını açıp IP, Erişim Tarihi, Erişim Metodu, Sorgu Stringi, Http versiyonu, Bağlantı kurulan portu, Yönlendiren Linki, Kullanıcı Browser Bilgisini rahatça okuyabileceğimiz bir formatta bize sunuyor.

prettify

Biraz daha eğlenceli hale getirelim mi? Dün ve bugün bu sayfayı ziyaret eden eşsiz kişi sayısı:

./00-prettify.php today yesterday | grep “IP” | sort | uniq | wc -l

ip-uniq-filter

Toplam sayfa ziyaret sayısı.

./00-prettify.php today yesterday | grep -P “^REF” | grep -v -e “wp\-cron” -e “wp\-admin” | wc -l

sayfa-ziyaret

LikedIn üzerinden gelen ziyaretçi sayısı ve benzersiz ziyaretçi sayıları.

./00-prettify.php today yesterday | grep -P “^REF” | grep -v -e “wp\-cron” -e “wp\-admin” | grep “linked\|lnkd” | wc -l

./00-prettify.php today yesterday | grep -P “^REF” | grep -v -e “wp\-cron” -e “wp\-admin” | grep “linked\|lnkd” | uniq | wc -l

BenchMark:

start=$(date +"%s.%N") && \
./00-prettify.php all | grep -P "^REF" | grep -v -e "wp\-cron" -e "wp\-admin" | grep "linked\|lnkd" | uniq | wc -l && \
end=$(date +"%s.%N") && \
echo "$end - $start" | bc

Her ne kadar keskin bir ölçüm olmasa da fikir vermesi açısından 1vCPU, 1GB Ram VPS üzerinde ~480K satır log içerisinden 1 php script ve 5 pipe içeren kıstasta sorguyu “0.46 saniye” gibi bir sürede getirebiliyor!

bench

Kalın sağlıcakla;

Daha da ileri seviyeye gitmek isteyen arkadaşlar için aşağıda linkini verdiğim site paha biçilemez:

https://www.regular-expressions.info/

https://regex101.com/

Bir başka yazıda görüşmek üzere, Allah’a emanet olun.

Yorumlar kapatıldı.