Linux İşletim Sistemi/Linux Komutları/Düzenli ifadeler

Düzenli ifadeler bir bilgisayar programcısının en çok seveceği yapılardandır. Çünkü programcıların normalde yapamayacağı veya çok zor yapacağı şeyleri son derece basit ve kullanışlı yöntemlerle yapabilmesini sağlar.

Yukarıdaki tanımdan düzenli ifadelerin ne demek olduğunu anlamadınız, biliyorum. O halde açıklayayım. Düzenli ifadeler, bazı ortak özelliklere sahip karakter dizeleriyle ilgili ortak işlemler yapabilmemizi sağlayan bir yapıdır. Örneğin iki karakter dizesi de a harfi ile başlıyorsa bunların ortak özelliği a harfi ile başlamalarıdır diyebiliriz ve bu ortak özelliğe dayanarak bu iki karakter dizesiyle ilgili ortak işlemler yapabiliriz. Örneğin ikisini de başka bir kelimeyle değiştirebiliriz.

Linux komut satırında düzenli ifadeler grep, sed ve awk komutlarıyla kullanılabilir. grep komutunu daha önce görmüştük. sed ve awk komutlarını da bu bölümde göreceğiz. Şimdi düzenli ifadelere başlayabiliriz.

Köşeli parantezler: [ ]

değiştir

Köşeli parantezler ilgili yerde parantezin içindeki karakterlerden birisinin bulunması gerektiğini belirtir. Örnek:

grep [ktsş]an dosya.txt

Bu komutla dosya.txt dosyasında kan, tan, san ve şan kelimeleri aranır. Köşeli parantezlerin başka bir kullanımı da şu şekildedir:

grep [a-z]an dosya.txt

Bu komutla aan'dan zan'a kadar olan kelimeler aranır. (aan, ban, can, çan, ..., zan)

grep [f-m]an dosya.txt

Bu komutla fan'dan man'a kadar olan kelimeler aranır. (fan, gan, ğan, han, ..., man)

grep [1-9]an dosya.txt

Bu komutla 1an'dan 9an'a kadar olan kelimeler aranır. (1an, 2an, 3an, 4an, ..., 9an)

grep [4-7]an dosya.txt

Bu komutla 4an, 5an, 6an ve 7an aranır.

NOT: Yaptığım denemelerde büyük harf kullanımıyla küçük harf kullanımının genelde fark etmediği gördüm. Sadece bir yerde fark ediyor. Eğer köşeli parantezler içindeki ilk harf küçük harfse o harfin büyük harf olarak karşılığıyla başlayan karakter dizesi çıkmıyor. Bu sorunu köşeli parantezler içindeki ilk harfi büyük yaparak giderebilirsiniz.

Nokta: .

değiştir

İlgili yerde herhangi bir karakter bulunması gerektiğini belirtir. Örnek:

grep ş.n dosya.txt

Bu komutla şan ve şen gibi ş ile başlayıp n ile biten ve üç karakterden oluşan bütün karakter dizeleri aranacaktır. Başka bir örnek:

grep f...r dosya.txt

Bu komutla bulunabilecek karakter dizelerinden bazıları: fener, fecir, fular, fiber, f)? r

Yıldız: *

değiştir

Kendinden önceki karakterin (veya köşeli parantez ifadesinin) hiç bulunmayabileceğini, bir kere veya daha fazla bulunabileceğini belirtir. "O halde bunun aramaya bir etkisi yoktur" diyebilirsiniz, ki haklısınız. Evet, aramaya etkisi yoktur ama bulunan karakter dizesine etkisi vardır. Şimdi içeriği şöyle olan bir metin dosyası oluşturun ve adına dosya.txt verin:

benim adım hmet
benim adım ahmet
benim adım aahmet
benim adım aaaaaahmet

Komut isteminde bu dosyanın bulunduğu klasöre girin ve şu komutu verin:

grep a*hmet dosya.txt

Bu komutun çıktısı şöyle olacaktır:

benim adım hmet
benim adım ahmet
benim adım aahmet
benim adım aaaaaahmet

Gördüğünüz gibi * işareti "bulunabilme"yi değiştirmiyor, ama bulunan karakter dizesini değiştiriyor. Bu işaret bulunan bir karakter dizesinin başka bir karakter dizesiyle değiştirilmesini sağlayan ileride göreceğimiz sed komutunda işe yarayacak. Başka bir örnek:

grep [a-z]*m  dosya.txt

Bu komutun çıktısı da şöyle olur:

benim adım hmet
benim adım ahmet
benim adım aahmet
benim adım aaaaaahmet

^ işareti

değiştir

grep komutuna argüman olarak verilen aranan metinin yalnızca başına konursa işe yarar. Anlamı şudur: bu karakter dizesini yalnızca satır başlarında ara. Şimdi dosya.txt dosyanızın içeriği şöyle değiştirin:

ahmet başta
ortada ahmet...
sonda ahmet

Şimdi şu komutu verelim:

grep ^ahmet dosya.txt

Bu komut sonucunda yalnızca ilk satır görüntülenir.

NOT: Şöyle bir komut istediğimiz aramanın yapılamamasına yol açar.

grep b^ahmet dosya.txt

$ işareti

değiştir

^ işaretinin tam tersidir. Arama yalnızca satır sonlarında yapılır. Örnek:

grep ahmet$ dosya.txt

Bu komut sonucunda ekranda yalnızca son satır görüntülenir. Komutun işe yaraması için $ işareti aranan metnin sonuna konmalıdır.

İki tane ters slash işareti (\\) düzenli ifadeler bakımından anlamı olan özel karakterlerin ([ ] . * ^ $) düz metin olarak algılanmasını sağlar. Diyelim ki metin dosyanızda ahmet$ metnini aramak istiyorsunuz. Ancak aramanızı bu şekilde, olduğu gibi yaparsanız komut istemi sizin sonu ahmet ile biten satırları aramak istediğinizi düşünecektir. İşte bunu engellemek için bir özel karakter olan $'ın önüne iki tane ters slash koyup $ karakterinin normal karaktermiş gibi algılanmasını sağlarız. Örnek:

grep ahmet\\$ dosya.txt

Bu komutla "ahmet$"lar aranır.

NOT: Eğer \ işaretinin düz karakter olarak algılanmasını istiyorsanız önüne üç tane \ koyarsınız. Yani yan yana dört tane \ karakteri bir tane düz \ karakteri anlamına gelir.

NOT: Arama yaparken özel karakterleri özel bir anlama gelmeyecek şekilde kullanırsak (örneğin arama metninin ortasında ^ kullanırsak) ilgili özel karakter düz karakter olarak algılanır. Ancak istersek yine de \\ ile düz karakter olarak algılanmalarını sağlayabiliriz. Örnekler:

grep be^nim dosya.txt
grep ad$ı dosya.txt
grep *ben dosya.txt

NOT: Arama yaparken [ veya ] karakterlerinden sadece birini kullansak bile \\ ile düz karakter olarak algılattırmalıyız.

NOT: grep ile arama yaparken arama metnimizin başında $ bulunması gerekiyorsa bunun düz metin olarak algılanması için tek ters slash işareti kullanırız. Örnek:

grep \$benim dosya.txt

Bu komut sonucunda "$benim"ler aranır.

sed komutu

değiştir

sed komutu metin dosyalarının içeriğini değiştirmek için kullanılır. grep komutunu anlatırken söylediğimiz her şey sed için de geçerlidir. Yani [] . * ^ $ ve \\ işaretleri grep ile kullanıldığı şekilde sed ile de kullanılır. Ancak sed komutunun kendine özgü kuralları da vardır. Örnek bir komut:

sed s/benim/senin/ dosya.txt

Bu komutla dosya.txt dosyasındaki "benim"ler "senin" olarak değiştirilir. Daha doğrusu değiştirilmişi ekrana yazılır, orijinal dosya değiştirilmez. Ancak orijinal dosyanın da değiştirilmesi daha önce görmüş olduğumuz komutlar sayesinde oldukça kolaydır:

sed s/benim/senin/ dosya.txt > dosya2.txt
rm dosya.txt
mv dosya2.txt dosya.txt

Burada ilk komutun çıktısını direkt olarak dosya.txt dosyasına yazamadık. Çünkü dosya.txt dosyası zaten komut isteminde kullanımda olduğundan o tür bir kullanım istemediğimiz sonuçlara yol açıyordu. Şimdi dosya.txt dosyanızın içeriğini şununla değiştirin:

benim adım hmet
benim adım ahmet
benim adım aahmet
benim adım aaaaaahmet

Şimdi şu komutu verin:

sed s/a*hmet/osman/ dosya.txt

Bu komutun çıktısı şöyle olur:

benim adım osman
benim adım osman
benim adım osman
benim adım osman

Şimdi dosya.txt dosyanızın içeriğini şununla değiştirin:

1. deneme deneme deneme
2. deneme deneme deneme
3. deneme deneme deneme
4. deneme deneme deneme
5. deneme deneme deneme

Şimdi şu komutu verin:

sed s/deneme/mustafa/ dosya.txt

Evet, bu komut sonucunda beklenmedik bir çıkış aldınız. sed komutu her satırdaki yalnızca ilk "deneme"yi değiştirdi. İşte bu sed komutunun bir özelliğidir. sed komutu belirtilen metinle eşleşen her satırdaki yalnızca ilk metni değiştirir. Ancak bu sınırlamayı aşmak mümkündür:

sed s/deneme/mustafa/g dosya.txt

Gelelim sed komutunun başka bir özelliğine. Diyelim ki metin dosyasındaki bütün "deneme"ler yerine sadece 5. satırdaki "deneme"leri değiştirmek istiyorsunuz. Bunu yapmak için:

sed /5/s/deneme/mustafa/g dosya.txt

Bu komut bilgisayara şunu der: Önce "5" metninin olduğu satır(lar)ı bul. Sonra, bu satır(lar)daki "deneme" ile eşleşen metinleri "mustafa" olarak değiştir. Şimdi aynı konuyla ilgili başka bir örnek yapalım. dosya.txt dosyamızın içeriği şöyle olsun:

aaadenemebbb
aaadenemccc
qwertyfrdsx
deneme e

Komutumuzda şöyle olsun:

sed /deneme/s/a/e/ dosya.txt

Bu komut, içinde "deneme" olan satırlardaki yalnızca ilk "a"ları "e" olarak değiştirecektir. İlk ve son satırlarda "deneme" var. Ama son satırda "deneme" olmasına karşın hiç "a" yok. Bu durumda komut sadece ilk satırda etkisini gösterecektir. Bir de şöyle bir komut girelim:

sed /deneme/s/e/a/ dosya.txt

Burada dikkatimizi çeken şey ön arama yapılan kelimedeki harfin değişmesi. Yani böyle bir şey olabiliyor. Şimdi sed komutunun başka güçlü bir özelliğine geçelim: sed komutuyla tek seferde birden fazla değiştirme işlemi yapabilirsiniz. Örnek:

sed -e s/deneme/mustafa/g -e s/ayşe/fatma/g dosya.txt

Bu komutla dosyadaki "deneme"ler mustafa olarak, "ayşe"ler fatma olarak değiştirilir. "-e"ler artırılarak aynı komutla ikiden de fazla değişiklik yapılması sağlanabilir.

& işareti

değiştir

& işareti sed komutuna özgü bir karakterdir. Bulunan metni temsil eder. Biliyorsunuz, düzenli ifadeleri kullandığımızda aramak için yazdığımız şey çıkan sonuçlardan farklı oluyor. Örneğin ".an"ı aramışsak "tan", "şan" vb. çıkıyor. Bunlar ".an"dan farklı şeyler. İşte & işareti buradaki tan, şan, ve benzerlerini temsil ediyor. Örneğin dosya.txt dosyamızın içeriği şöyle olsun:

mustafa yılmaz
ayşe matur
süleyman uysal
tuğçe kazakçı
yusuf fişek 

Şimdi biz burada her ismin başına "sayın" ifadesini getirmek istiyoruz. Bunu şimdiye kadar öğrendiğimiz bilgilerle yapamayız. Bunu ancak & işaretini kullanarak yaparız:

sed s/.*/"sayın "\&/ dosya.txt

Bu komutun çıktısı şöyle olur:

sayın mustafa yılmaz
sayın ayşe matur
sayın süleyman uysal
sayın tuğçe kazakçı
sayın yusuf fişek

NOT: sed komutuyla da tıpkı grep gibi birden fazla dosyayla çalışılabilir. Ancak komutun çıkışını tekrar her bir dosyaya geri göndermek pek mümkün olmadığı için çıktıları tek ve farklı bir dosyaya almak daha mantıklı olacaktır.

awk komutu

değiştir

Şimdiye kadar grep ve sed komutlarını gördük. grep bulmaya, sed de bulup değiştirmeye yarıyordu. Ancak bu iki komutun birçok ortak yönleri vardı, ikisi de aynı mantığı kullanıyordu. Aralarındaki tek fark birisinin sadece aramaya yapması, birisinin de hem arama yapıp hem de değiştirmesiydi. Ancak şimdi göreceğimiz awk komutu bu iki komuttan tamamen farklı bir mantığa sahip. Şimdi dosya.txt dosyasının içeriğini şunlarla değiştirin:

benim adım osman, ya seninki ne
benimki de ayşe
memnun oldum
ben de memnun oldum

Şimdi şu komutu verelim:

awk '{print $1}' dosya.txt

Bu komutun çıktısı şöyle olur:

benim
benimki
memnun
ben

awk komutu belirtilen dosyayı okur. Dosyadaki boşlukla ayrılan her kelimeyi bir değişkene atar. Biz de özel karakterler kullanarak bu değişkenlere erişebiliriz. Yukarıdaki komut: "dosya.txt dosyasındaki her satırdaki birinci kelimeyi ekrana yaz" anlamına gelir. Başka bir örnek:

awk '{print $1" örnek deneme "$3}' dosya.txt

Bu komutun çıktısı da şöyle olur:

benim örnek deneme osman,
benimki örnek deneme ayşe
memnun örnek deneme
ben örnek deneme memnun

Gördüğünüz gibi " ve " arasına alıp normal metinleri de ekrana yazdırabiliyoruz. $3 de her satırdaki üçüncü kelime anlamına geliyor. Şimdi diyelim ki dosyadaki her satırla çalışmak istemiyoruz. Yalnızca istediğimiz satırdaki belirttiğimiz sütunları (kelimeleri) ekrana yazdırmak istiyoruz. Daha doğrusu istediğimiz satırlardaki istediğimiz kelimeleri awk değişkenlerine atamak istiyoruz. Bunun için:

awk '/benim/{print $2" "$3}' dosya.txt

Bu komut ile yalnızca içinde "benim" olan satırlar awk değişkenlerine atanır. Çıktısı şu şekildedir:

adım osman,
de ayşe

NOT: Düzenli ifadeler awk komutuyla da kullanılabilir. awk komutunda az önce "benim" yazdığımız yere bir düzenli ifade de yazabilirsiniz.

Şimdi boş bir klasöre uzantısı txt ve jpg olan dosyalar koyun, o klasöre girin ve şu komutu verin:

ls | awk -F"." '/txt/{print "mv "$1"."$2" "$1".doc"}' | bash

Komutun ayrıntılı açıklaması:

ls |

Bu kısımla ls komutunun çıktısını awk komutuna verdik.

-F"."

awk komutunun bu kısmıyla kelime ayırıcı karakterin boşluk yerine . (nokta) olacağını belirledik. Yani awk komutu, dosyadaki metni noktaya göre parçalayacak.

 '/txt/

Bu kısımla yalnızca içinde txt geçen satırlarla işlem yapılmasını sağladık.

{print "mv "$1"."$2" "$1".doc"}'

Bu kısımla ls komutunun listelediği her bir dosya için mv dosya.txt dosya.doc metninin üretilmesini sağladık.

| bash

Bu kısımla da awk komutunun verdiği çıktının ekrana yazılması yerine bash'e yani komut satırına komut olarak verilmesini sağladık. Sonuçta klasörümüzdeki txt uzantılı dosyaların uzantısı doc olarak değişecektir.

NOT: | bash yöntemi ekrana çıktı veren her komutla kullanılabilir. Başka bir örnek:

pwd | awk '{print "şu anda "$1" klasöründeyim."}'

NOT: ls komutunun sonucu başka bir komuta giriş olarak verilirken her dosya/klasör ismi farklı bir satırda sayılır. Yani ekranda gösterildiği gibi yan yana sayılmaz.

NOT: awk komutunu bir dosyayla kullanırken awk komutu, dosyadaki işlenen satır sayısı kere çalıştırılır. Örnek:

awk '{print "deneme"}' dosya.txt

Bu komutla ekrana dosya.txt dosyasındaki satır sayısı tane alt alta deneme yazılacaktır. Başka bir örnek:

awk '/ahmet/{print "deneme"}' dosya.txt

Bu komutla ise içinde ahmet olan satırlar kadar deneme alt alta yazılacaktır. Yani awk komutunun temel algoritması şudur:

  1. x=0
  2. x'i 1 artır.
  3. Bana verilen dosyanın veya komut çıktısının x. satırını oku.
  4. Bana ön arama için bir karakter dizesi verildi mi? Eğer verildiyse bir sonraki adıma geç. Verilmediyse 6. adıma geç.
  5. x. satırda bana ön arama için verilen kelime geçiyor mu? Geçiyorsa bir sonraki adıma geç, geçmiyorsa 2. adıma geç.
  6. $1, $2, $3, ... değişkenlerine x. satırdaki 1'inci, 2'nci, 3'üncü... kelimeleri ata.
  7. print içindeki ifadedeyi ekrana yaz. (veya dosya veya bash'e gönderilmesi gerekiyorsa oralara gönder)
  8. Bize verilen dosyanın veya komut çıktısının son satırında mıyız? Son satırındaysak çık. Son satırında değilsek 2. adıma git.