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ştirKöş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ştirKendinden ö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ştirgrep 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.
\\
değiştirİ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ştirsed 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:
- x=0
- x'i 1 artır.
- Bana verilen dosyanın veya komut çıktısının x. satırını oku.
- Bana ön arama için bir karakter dizesi verildi mi? Eğer verildiyse bir sonraki adıma geç. Verilmediyse 6. adıma geç.
- 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ç.
- $1, $2, $3, ... değişkenlerine x. satırdaki 1'inci, 2'nci, 3'üncü... kelimeleri ata.
- print içindeki ifadedeyi ekrana yaz. (veya dosya veya bash'e gönderilmesi gerekiyorsa oralara gönder)
- 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.