Komut Satırı & Bash

Awk ve Sed Komutları İle Pratik İşlemler

Güncelleme:
datamash yazımda awk ve sed komutlarına referansta bulunmuştum. Genel olarak metin işlemlerinde dair örnekler barındıran sed ve awk yazılarını AWK ve SED Komutları İle Pratik ...
GÖRSEL
datamash yazımda awk ve sed komutlarına referansta bulunmuştum. Genel olarak metin işlemlerinde dair örnekler barındıran sed ve awk yazılarını AWK ve SED Komutları İle Pratik İşlemler başlığı altında datamash benzerlikleri ve farklılıkları ile yeniden ele almak, basit veri işlemleri ile komut örneklerini zenginleştirmek istiyorum.

Awk ve Sed Komutları

Örneklere geçmeden önce komutlarla ilgili kısa bir hatırlatma yapayım. Awk, örüntü temelli tarama ve işleme dilidir. Evet, komut kullanımının dışında kapsamlı işlemler yapmanızı mümkün kılan bir dil olma özelliğine sahiptir. NAWK ve GAWK adında daha gelişmiş yeni versiyonları / alternatifleri bulunur. Sed,lListeleme, işaretleme, sorgulama, örüntülerle metin işlemleri gerçekleştirme gibi özelliklere sahiptir. Şimdi bu kısa açıklamalara dair örnekler görelim.

Awk ve Sed İle Temel İşlemler

cat komutu ve pipe aracılığıyla dosyalarımızı ilgili işlemlere dosya adını açık bir şekilde belirterek dahil etmiştik. Bu yazı altında, önceki örnekleri de zenginleştirmek adına dosya adını bir değişken üzerinden kullanacağım.

Değişken Tanımlama

İşlemimiz oldukça basit; dosyamızın adı test.txt, değişkenimizin adı table olsun ve dosyamız işlemleri gerçekleştirdiğimiz dizinde bulunsun. table sadece değer olarak test.txt ifadesine sahip olacağı için dosyamızın bu aşamada oluşturulmuş olması gerekmiyor.
table="test.txt"
Dosya eğer farklı bir dizinde yer alıyorsa, çağırdığımız zaman hata dönmemesi için aşağıdaki örneklerde olduğu gibi dosyanın net bir şekilde bulunduğu dizini belirtmeliyiz.
table="./klasor/test.txt"
table="../test.txt"
table="/Users/[kullanici-adi]/Desktop/test.txt"
table değişkenini test edelim. Bunun için $ işaretini kullanabiliriz. Bu işaret sonrasında belirttiğimiz ifadenin bir değer taşıdığını gösterir. Aşağıda farkı görebilmeniz için iki farklı şekilde işlem gerçekleştireceğim.
echo table
Bu komutu uyguladığımızda echo ekrana belirttiğimiz ifadeyi yazdırır ve komutun dönüşü yine table olur.
echo $table
$ kullandığımızda ise echo belirtilen ifadenin bir değişken olduğunu görür ve değişkenin sahip olduğu karşılığı yazdırır. Dolayısıyla yukarıda belirttiğimiz dosya yolu dönecektir. Dosyanın içeriği herhangi bir şekilde işleme alınmaz. Dolayısıyla dosya içeriğinde değişiklikler söz konusu ise değişkeni her çağırdığımızda yeni içeriği işleme almış oluruz. Benim aldığım dönüş şu şekilde oldu:
/Users/[kullanici-adi]/Desktop/test.txt
Unutmadan, tanımlanan tüm değişkenleri set komutunu kullanarak listeleyebilirsiniz.
set
Komut uygulandıktan sonra yarattığımız table değişkeni de dahil olmak üzere ilgili oturumda kullanılabilecek tüm değişkenler listelenecektir. Bu listede yer alan bir değişkeni unset komutuyla silebiliriz.
unset table
env tanımı yapmadığımız için değişkenlerimiz ve aldıkları değerler oturumumuz sürecinde geçerli olacaklardır.Bu açıklamaların hemen ardından, veri işlemleri için kullanacağımız test.txt dosyamızın da içeriğine bir göz atalım.
OrderDate;Region;Rep;Item;Units;UnitCost;Total
1.6.18;East;Jones;Pencil;95;1.99;189.05
1.23.18;Central;Kivell;Binder;50;19.99;999.50
2.9.18;Central;Jardine;Pencil;36;4.99;179.64
2.26.18;Central;Gill;Pen;27;19.99;539.73
3.15.18;West;Sorvino;Pencil;56;2.99;167.44
4.1.18;East;Jones;Binder;60;4.99;299.40
4.18.18;Central;Andrews;Pencil;75;1.99;149.25
5.5.18;Central;Jardine;Pencil;90;4.99;449.10
6.6.18;East;Jardine;Pencil;95;5.99;189.05
6.13.18;West;Kivell;Binder;50;1.99;999.50
7.19.18;Central;Kivell;Pencil;96;4.99;179.64
7.6.18;Central;Gill;Pencil;27;9.99;539.73
7.5.18;East;Jones;Binder;56;3.99;167.44
8.13.18;West;Gill;Pencil;60;49.99;299.40
9.28.18;East;Andrews;Pen;75;12.99;149.25
9.25.18;Central;Jardine;Pencil;20;4.99;449.10
17 satır (row) ve 7 sütundan (col) oluşan değerler comma separated (;) olarak ayrılmış durumda ve ilk satırımızda başlıklar (heading) yer alıyor. İlk olarak head ve tail komutlarıyla test.txt dosyasının içeriğine kabaca göz atalım.

Head ve Tail Kullanımı

head $table
Komutu uygulamamızın ardından şu dönüşü alırız:
OrderDate;Region;Rep;Item;Units;UnitCost;Total
1.6.18;East;Jones;Pencil;95;1.99;189.05
1.23.18;Central;Kivell;Binder;50;19.99;999.50
2.9.18;Central;Jardine;Pencil;36;4.99;179.64
2.26.18;Central;Gill;Pen;27;19.99;539.73
3.15.18;West;Sorvino;Pencil;56;2.99;167.44
4.1.18;East;Jones;Binder;60;4.99;299.40
4.18.18;Central;Andrews;Pencil;75;1.99;149.25
5.5.18;Central;Jardine;Pencil;90;4.99;449.10
6.6.18;East;Jardine;Pencil;95;5.99;189.05
head ve tail aksi belirtilmediği sürece 10 değeri üzerinden işlem yürütürler. Yukarıda da görüldüğü üzere test.txt dosyamızın içeriğinde yer alan ilk 10 satır bize iletilmiş durumda. Şimdi de tail ile son 10 satırı alalım.
tail $table
Komut uygulandıktan sonra şu dönüşü alırız:
4.18.18;Central;Andrews;Pencil;75;1.99;149.25
5.5.18;Central;Jardine;Pencil;90;4.99;449.10
6.6.18;East;Jardine;Pencil;95;5.99;189.05
6.13.18;West;Kivell;Binder;50;1.99;999.50
7.19.18;Central;Kivell;Pencil;96;4.99;179.64
7.6.18;Central;Gill;Pencil;27;9.99;539.73
7.5.18;East;Jones;Binder;56;3.99;167.44
8.13.18;West;Gill;Pencil;60;49.99;299.40
9.28.18;East;Andrews;Pen;75;12.99;149.25
9.25.18;Central;Jardine;Pencil;20;4.99;449.10
Görüntülenmesini istediğimiz satır sayısını belirtmek istersek -n kullanırız.
head -n 5 $table
tail -n 5 $table
head kullanımında dikkatinizi çekmiştir, başlık da yine listeleme içerisinde yer almakta. Bir dosyanın başlık ve/ya toplam değerler barındırıp barındırmadığından emin olmak için head ve tail oldukça pratik ve işlevsel araçlardan biri. Başlıkları görüntülemeden değerleri listelemek istersek ne yapmalıyız?
tail -n+2 $table
Yukarıdaki komut ile 2. satırdan itibaren bize içeriği döndürecektir. Evet, başlığı artık görmüyoruz. Bu işlemi bir de sed ve awk ile gerçekleştirelim.

Awk İşlemleri

awk '{if(NR>1)print}' $table
awk '{if(NR!=1)print}' $table
awk '{if(NR==1){next}print}' $table
Yukarıdaki örneklerde 1,1d, '1!p' ve NR ifadeleri hep ilk satır sonrasının işleme alınması gerektiğini belirtmekteler.Tablodan kesit alma işlemini biraz daha farklılaştıralım ve random olarak ilgili dosya içerisinden bir dilim getirelim.
head -$((${RANDOM} % `wc -l < $table` + 10)) $table | tail -10
wc -l < $table bize word count verisi sunmakta ve -l ile satır değerini alabilmekteyiz. Dolayısıyla, yukarıdaki kodumuz bize ilgili dosya içerisinden rasgele bir alan belirlemekte ve +10 ile dilimin kapsamını tanımlamış olmaktayız. Bu sayede head ve tail ile edindiğimiz alanları tekrar görmemiş oluruz. Ek bir not, satır sayısını ayrıca şu şekilde de edinebiliriz.
awk 'END{print NR}' $table
İlgili dosya içeriğine hızlıca göz attık ve temel düzeyde bir bilgi edindik. Artık işlemlerimizi çeşitlendirebiliriz. Bu işlem için awk komutunu kullanacağım. Öncesinde temel bir bilgi eklemek istiyorum. awk komutu ile sütun olarak ifade edebileceğimiz ayrılmış alanları (space, tab space, noktalı virgül, virgül, pipe vb.) değişkenler olarak sıralı bir şekilde edinebilmekteyiz. test.txt dosyamız içerisinde 7 sütun olduğunu biliyoruz.
awk -F';' '{print NF; exit}' $table
Komut bize ; ile ayrıştırılmış alan sayısını verecektir. 7 değerini aldık, değil mi? $n ile de tüm sütunları yazdırabiliriz. Unutmadan, -F';' sütunların ; ile ayrılmış olduğunu ve işlemin buna göre gerçekleştirilmesi gerektiğini belirtmekte. Farklı bir ayrıştırıcı (örneğin pipe) kullanılmışsa ilgili alanın buna uygun şekilde düzenlenmesi gerekmektedir.
awk -F';' '{print $n}' $table
O halde doğru yoldayız ve $1, $2, $3..., $7 şeklinde her sütunu ayrı ayrı edinebildiğimize göre ilk sütun ile başlayalım.
awk -F';' '{print $1}' $table
Yukarıdaki komut ile 1. sütunu listelemiş oluruz. Örneğimizi head komutuna dair edindiğimiz bilgi ile pekiştirelim.
awk -F';' 'NR!=1 { print $3 }' $table | head -n 5
Yukarıdaki komut ; ile ayrıştırılmış sütunlarımızdan 3. olanı ilk satırı (başlık) es geçecek şekilde çekmekte ve bize sadece ilk 5 değeri döndürmekte. Yeni örneğimizde sütunları ayrı ayrı çekip | (pipe) ile birleştirelim.
awk 'BEGIN{FS=";"; OFS="|"} {print $1,$4,$6}' $table
Komuttaki FS kullanımdaki işareti OFS ise yeni işareti ifade eder. Ön tanımlı olarak OFS boşluk değerini tanımlar. Komutu uygulamamızın ardından alacağımız dönüş şu olacaktır. Bu komutu noktaları (.) virgül (,) olarak değiştirmek için de kullanabilirsiniz.
OrderDate|Item|UnitCost
1.6.18|Pencil|1.99
1.23.18|Binder|19.99
2.9.18|Pencil|4.99
2.26.18|Pen|19.99
3.15.18|Pencil|2.99
4.1.18|Binder|4.99
4.18.18|Pencil|1.99
5.5.18|Pencil|4.99
6.6.18|Pencil|5.99
6.13.18|Binder|1.99
7.19.18|Pencil|4.99
7.6.18|Pencil|9.99
7.5.18|Binder|3.99
8.13.18|Pencil|49.99
9.28.18|Pen|12.99
9.25.18|Pencil|4.99
Başlığı hariç tutmak isteyebiliriz.
awk 'BEGIN{FS=";"; OFS="|"} 'NR==1'{next} {print $1,$4,$6}' $table
Şimdi de edindiğimiz sütunlar üzerinde ek işlemler yapalım.
awk  -F';' 'BEGIN{FIELDWIDTHS="3 4 3"} NR==1 {next} {print $1,$2,$3}' $table
Komutu uyguladığımızda şu dönüşü alırız.
1.6 .18; Eas
1.2 3.18 ;Ce
2.9 .18; Cen
2.2 6.18 ;Ce
3.1 5.18 ;We
4.1 .18; Eas
4.1 8.18 ;Ce
5.5 .18; Cen
6.6 .18; Eas
6.1 3.18 ;We
7.1 9.18 ;Ce
7.6 .18; Cen
7.5 .18; Eas
8.1 3.18 ;We
9.2 8.18 ;Ea
9.2 5.18 ;Ce
Komut ; ile ayrıştırılmış alanları alıp ($1,$2,$3) bu alanlar içerisinden $1'in 3, $2'nin 4 ve $3'ün 3 karakterini bize verir. Bu işlemi gerçekleştiren FIELDWIDTHS tanımıdır. İşlem yaptığımız test.txt dosyasının bir nedenden dolayı bloklar halinde işlenmiş olduğunu varsayalım.
OrderDate;Region;Rep;Item;Units;UnitCost;Total
1.6.18;East;Jones;Pencil;95;1.99;189.05
 
OrderDate;Region;Rep;Item;Units;UnitCost;Total
1.23.18;Central;Kivell;Binder;50;19.99;999.50
 
OrderDate;Region;Rep;Item;Units;UnitCost;Total
2.9.18;Central;Jardine;Pencil;36;4.99;179.64
Bu durumdaki içerikten başlıklar dışındaki alanları seçmek ve başka bir dosyaya kayıt etmek istiyorum.
awk 'BEGIN{FS="\n"; RS=""} {print $2,$3}' $table > yeni.txt
Evet, komutu uyguladığımızda yeni satırlar değerlendirilmeye alınır FS="\n", ardından $2 alan (değerler) ve $3 ile boşluğun kendisi getirilir. Boşluğu değer olarak almak istemezseniz (başka bir tanım da olabilir) sadece değer satırını belirtmeniz yeterli olacaktır.
awk 'BEGIN{FS="\n"; RS=""} {print $2}' $table > yeni.txt
Çok basit bir değişiklik yapalım ve sonucu ne olacak görelim.
awk 'BEGIN{FS="\n"; RS=";"} {print $1}' $table
Komutu uygulamamızın ardından tüm alanları (satır ve sütunları) yeni satır olarak olarak alırız.
OrderDate
Region
Rep
Item
Units
UnitCost
Total
East
Jones
Pencil
95
1.99
189.05
Region
Rep
Item
Units
UnitCost
Total
Central
Kivell
Binder
50
19.99
999.50
Region
Rep
Item
Units
UnitCost
Total
Central
Jardine
Pencil
36
4.99
179.64
awk komutunu şartlı ifadelerle zenginleştirebiliriz. Tekrar ilk veri tablomuza dönelim ve aşağıdaki komutu uygulayalım.
awk -F';' 'NR==1{next}{if ($5 > 50) print $3}' $table
Edindiğimiz bilgiler çerçevesinde komutu incelediğimizde şu sonuca varıyoruz; ilk satırdan sonraki satırları değerlendirmeye al, 5. sütuna bak, 50'den büyükse ilgili satırın 3. sütun değerini dön.
Jones
Sorvino
Jones
Andrews
Jardine
Jardine
Kivell
Jones
Gill
Andrews
$3 yerine $n ifadesi kullansak sonuç ne olurdu?
1.6.18;East;Jones;Pencil;95;1.99;189.05
3.15.18;West;Sorvino;Pencil;56;2.99;167.44
4.1.18;East;Jones;Binder;60;4.99;299.40
4.18.18;Central;Andrews;Pencil;75;1.99;149.25
5.5.18;Central;Jardine;Pencil;90;4.99;449.10
6.6.18;East;Jardine;Pencil;95;5.99;189.05
7.19.18;Central;Kivell;Pencil;96;4.99;179.64
7.5.18;East;Jones;Binder;56;3.99;167.44
8.13.18;West;Gill;Pencil;60;49.99;299.40
9.28.18;East;Andrews;Pen;75;12.99;149.25
if...else... örneğimizi biraz daha genişletelim.
awk -F';' ' NR > 1 {if ($5>30){ x=$1; print x} else { x=$5/2; print x }}' $table
Komutu uyguladığımızda $5 değerinin 30 üzerinde olması durumunda $1 sütun değeri, aksi durumda $5 sütun değerinin yarısı yazdırılır. Şimdi de sütun sayısını ve belirli bir sütun değerlerinin toplamını alalım.
awk -F';' ' NR > 1 { sum += $5 }END{ print NR-1, sum }' $table
Komutun dönüşü şu şekilde olacaktır;
16 968
Sadece Binder değeri içeren satırları alalım.
awk -F';' '$4=="Binder"' $table
Şimdi son örneklerimize istinaden şöyle bir işlem gerçekleştirelim.
awk -F';' ' NR > 1 {sum=0;i=1;while(i<5){sum += $i;i++} average=sum/3; print "Average of",sum,":",average;}' $table
Komut satır sayısı kadar (başlık hariç) $i değerini döngü (while) içerisinde artırır ve sum değerini 3 ile bölerek average değişkenine atar. Ardından Average of ... şeklinde döker.
Average of 1.6 : 0.533333
Average of 1.23 : 0.41
Average of 2.9 : 0.966667
Average of 2.26 : 0.753333
Average of 3.15 : 1.05
Average of 4.1 : 1.36667
Average of 4.18 : 1.39333
Average of 5.5 : 1.83333
Average of 6.6 : 2.2
Average of 6.13 : 2.04333
Average of 7.19 : 2.39667
Average of 7.6 : 2.53333
Average of 7.5 : 2.5
Average of 8.13 : 2.71
Average of 9.28 : 3.09333
Average of 9.25 : 3.08333
while döngüsü yerine for da kullanabiliriz.
awk -F';' ' NR > 1 {sum=0;for(i=1;i<5;i++){sum += $i;i++} average=sum/3; print "Average of",sum,":",average;}' $table
Sonuç yine aynı oldu, değil mi? Şartlı ifadeler ve döngülerin ardından son olarak pivot işlemlerine değinmek istiyorum. İlk pivot örneğimiz yine başlığı hariç tuttuğumuz bir şekilde sütun $4 gruplandırılması üzerine olsun ve bunun karşılığı olarak da sütun $5'i alalım.
awk -F';' 'NR>1 {pivot[$4]+=$5} END{for (x in pivot) print x";"pivot[x]}' $table
Komutumuzun uygulanmasının ardından şu dönüşü alırız.
102;Pen
650;Pencil
216;Binder
Bu işlemi datamash ile gerçekleştirecek olsak komutumuz şöyle olurdu:
cat $table | datamash -st ';' --header-in groupby 4 sum 5
datamash -st ';' --header-in groupby 4 sum 5 < $table | datamash -st ';' reverse
Son olarak, örneğin Binder için toplamın doğruluğunu kontrol etmek istediğimizi varsayalım;
awk -F';' ' NR > 1 {if ($4=="Binder"){ sum += $5;}}END {print sum}' $table
If...Else... kullanımında bir kısa yol mevcut; ~ /[deger]/ ile belirlenen şartın sağlanması durumunda süslü parantez ({}) içerisinde belirtilen işlem uygulanır.
awk -F ';' '$4 ~ /Binder/ {sum += $5} END {print sum}' $table

Sed İşlemleri

Awk örneklerinin başında başlık alanı olarak ifade edebileceğimiz ilk satırı nasıl göz ardı edebileceğimizden bahsetmiştim. Aynı işlemi sed ile tekrarlayalım;
sed 1,1d $table
sed -n '1!p' $table
Evet, komutu uyguladığımızda OrderDate;Region;Rep;Item;Units;UnitCost;Total içeriği es geçilecektir. Bu işlemin yanı sıra ayrıştırıcı olarak kullandığımız noktalı virgülü (;) pipe (|) ile değiştirelim.
sed -i 's/;/|/g' $table
Komutu uyguladığımızda $table ile çağırdığımız test.txt dosyasının içeriğinde yer alan noktalı virgüller (;) pipe (|) olarak değiştirilecektirler. Ancak, bir not olarak eklemekte fayda var. Yukarıdaki komutu uyguladığınızda büyük ihtimalle command c expects \ followed by text şeklinde bir hata alacaksınız. Bunun nedeni dosya içerisinde yer alan bir ifadenin komut kabul edilmesi. Bu hatayı şu şekilde önleyebiliriz. -i işlemin çıktı yerine dosya üzerinde gerçekleştirilmesini sağlayacaktır. -e kullanmamızın nedeni ise sed'in seçenek olmayan ilk parametreyi komut dosyası olarak kullanmaya çalışmasıdır. Yukarıdaki hatanın da nedeni budur.
sed -ie 's/;/|/g' $table
$table içeriğini listeleyelim, ancak Örüntüler kullanarak 9 ile başlayan satırları bu listemenin dışında tutalım.
sed '/^9.*/d' $tableKomut uygulandıktan sonra istediğimiz çıktı ekranda dönecektir ve dosya içeriğinde bir değişiklik söz konusu olmayacaktır. Ancak, dosyanın da güncellenmesini istersek -i parametresini de komuta dahil edebiliriz.

Son Olarak

Elbette ilgili komut örneklerini daha da çoğaltmak, çeşitlendirmek mümkün. Aklıma geldikçe eklemeler yapmaya devam edeceğim. Bu süreçte değerlendirmek üzere GNU > Datamash Alternatives sayfasını incelemenizi öneririm. Ayrıca, liste olarak eklediğim bağlantılar aracılığıyla da pek çok alternatif örneğe ulaşabilirsiniz.

HABERDAR OL

Yeni eklenen projeler, eğitimler, içerikler ve yayınlanan videolar e-posta adresine gelsin.