Shell Scripting
|Shell Scripting
Merhabalar arkadaşlar bu yazıda linux/unix sistemlerde shell scripting (bash) ile ilgili başlangıç niteliğinde bir kaç şey anlatmaya çalışacağım.
Öncelikle yeni başlayanlar için en sık kullanılan terminal komutlarının nasıl kullanıdığının anlatıldığı ŞU yazıyı okumadıysanız bir göz atmanızda fayda var.
Başlangıç olarak kullanımını bilmeniz gereken bir kaç komutu listelemem gerekirse ;
ls, pwd, grep , wc, more, cat, cd, echo, man…
İlk olarak bir shell script tam olarak nedir ne iş yapar buna cevap vermeden önce , bir shell script örneği göstermek istiyorum.
————-kare.sh————————
#!/bin/bash [ -z $1 ] && $0 1 && exit echo -n `$0 ((1+$1*2+2))` $#
bunun çıktısı ne olabilir ?
——————————————–
Eğer daha önce bash script görmediyseniz, duymadıysanız bu ne falan demiş olabilirsiniz. Ama yazının ilerleyen bölümlerinde çok basit bir kaç algoritmayı bash script’le uygulayıp , bir kaç script yazdığımızda çok iyi anlayacağınızı umuyorum.
Shell nedir ? ne işe yarar ? Bash nedir ? gibi soruların cevaplarını vermek istiyorum öncelikle.Bunlarla ilgilenmiyorsanız hemen BASH SCRIPTING başlığından devam edebilirsiniz.
Shell, kullanıcı programlarını (scriptleri) yorumlayıp , çıktı üreten bir kabuktur. Yanlış duymadınız derlemiyor sadece yorumluyor. Birçok türevi var bourne shell (sh) , bourne again shell (bash), c shell (csh) vs…
Bu scriptler aslında bir text dosyasından başka birşey değil. Bu scriptler sayesinde; aritmetik işlemler, girdi çıktı işlemleri , dosya okuma- yazma gibi manipulasyonlar, vs olabilir ve aynı zamanda sistem komutlarını scriptlerinizde kullanabilmeniz mümkün. Hatta terminalde çalıştırdığınız komutların (ls,pwd,cd,cat,more…) benzerlerini veya daha gelişmişlerini yazabilirsiniz.
BASH SCRIPTING
Öncelikle yazdığınız scripleri nasıl çalıştıracağınızı bilmeniz gerekiyor. Bunun iki yolu var biri terminalden direk shell’e elinizdeki script dosyasını yorumlamasını söylemek, diğeri ise executable bir dosya gibi çalıştırmak.
Örneğin bizim dosyamızın adı “ilk_Bscript” olsun , terminale ;
$-> bash ilk_Bscript
yazmamız bu scriptin yorumlanıp çalışması için yeterli. Yada scriptin ilk satırına “#!/bin/bash” yazmamız aynı işi görecektir. Çünkü shell bu dosyayı executable olarak çalıştırmak istediğinizde ilk satırdaki yola bakarak bu dizindeki bash binary’si ile ilişkilendirecek ve bin dizini altındaki bash bundan sonrasını yorumlayacaktır.
executable bir dosyayı ; $-> ./ilk_Bscript
şeklinde çalıştırabilirsiniz.Burada dikkat etmeniz gereken dosyanızın executable izninin olması. Dosyaların üç farklı işlem için user, group ve others için izinleri bulunur .Bu izinler (read ,write,execute) dosyanın hangi kullanıcı veya group tarafından okunabilir , yazılabilir veya çalıştırılabilir olduğunu denetleyen basit bir hiyerarşidir. Bütün kullanıcılara bütün izinleri vermek için terminale dosyanın bulunduğu dizinde “chmod 777 ilk_Bscript” yazmanız yeterli, yada izinlerini kendi isteğinize göre düzenleyebilirsiniz.
Peki şimdilik bu dosya boş, hemen bir kaç satır yazalım ve bakalım.
———–ilk_Bscript————
#!/bin/bash x=5 echo "5+4" echo elektrobot.net echo x echo $x y=x+4 echo $y y=$x+4 echo $y y=$(( y+1 )) echo $y y=$(( $y+1 )) echo $y y=$(( $x+4 )) echo $y
———-Output——————
5+4
elektrobot.net
x
5
x+4
5+4
10
11
9
————————————
Öncelikle “echo” kendisine parametre olarak verilen veriyi standart çıkışa yazar.
Standart çıkış default olarak terminaldir. Dolayısıyla ekrana basacak. Kodu anlamak için echo ları referans alarak kodu satır satır inceleyip çıktıyla karşılaştırmanız yeterli. Az çok ne yaptığını anlayacaksınız.
Shellde değişkenler tanımlanabilir ve bu değişlenlere değer atanabilir. “x=5” , x değişkenine 5 değeri atanır. Ancak bu değişkenin türünün (int yada string olarak ) belirtilmemiş olduğuna dikkat edin. Eğer özellikle değişken tipini belirtmek istiyorsanız ;
declare -i x=5 # integer olarak tanımlandı
declare -a yeniarray(cg cenap ) # array olarak tanımlandı, dizinin ilk elemanı cg ikinci elemanı cenap
bu şekilde yapabilirsiniz. Neyse konumuza dönecek olursak, bir değişkeni kullanabilmek için başına “ $ ” işareti koymamız gerekiyor. Aksi halde shell sizin değişkeni mi yoksa rastgele bir stringi mi belirttiğinizi anlamaz. Ve doğal olarak “echo x” teki gibi ekrana x yazacaktır. Tamam bunu anladım ama “y=x+4” ataması nasıl oluyor diye sorabilirsiniz. Zaten y değişkenine burada bir string değer atanmış oluyor(“x+4” stringi). “y=$x+4” aynı şekilde bir stringden başka birşey değil, ama string “5+4” olacak ,(5 nerden geldi?$x değişkeninin tuttuğu değer olan 5)
Aritmetik bir toplama işlemi az önceki atamalarda söz konusu değil. Aritmetik işlemleri shell’in anlamasını istiyorsanız çift parantez “ (( ifade )) ” yada köşeli parantez [] kullanabiliriz. Buradaki ifade toplama çarpma vs olabilir.
Örn:
x=$(( 5+4 ))
x=$[ 5+4 ]
(( )) ———> bu parantezler içinde c dili gibi aritmetik operatörleri kullanarak ifade(expression) yazabilirsiniz.
dolayısı ile yukarıdaki örneğin tam olarak ne yaptığını az çok anlamış olduğunuzu umarak devam ediyorum.
Dipnot: (( )) aynı zamanda integer’lar için koşullu ifadeleri (karşılaştırma vs.) bu parantezler ile sağlayabilirsiniz.Bu şekilde bir koşullu ifade koşarsanız, koşul doğru ise 0 değilse 0’dan farklı bir değer geri döndürür. Stringler için karşılaştırma vs. yapmak istiyorsanız [[ ]] köşeli parantezler devreye giriyor.
Şimdi komut satırından argüman verme işi nasıl oluyor ona bakalım.
Örneğin grep komutuna yada cd komutuna argüman verdiğimizde kod içerisinde nasıl algılanıyor buna bakalım.
shell’de önceden tanımlanmış değişkenler (predefined variables) olarak geçen ve bu konuda yardımına başvuracağımız değişkenler bulunuyor. Bunlar;
$$ -> shell’in PID’si (Process IDentifier)
$? -> en son koşan komutun return değeri (koşma başarılı mı değil mi buradan anlayabiliriz)
$0 -> scriptin adı
$1 – $9 -> scriptin 1-9 arasındaki argümanları , örneğin $1 ilk argüman
$* -> scriptin $0 haricindeki tüm argümanlarının string hali
$@ -> scriptin $0 haricindeki tüm argümanlarının array(dizi) hali
$# -> scriptin argüman sayısı
————-ikinci_Bscript————————————————
#! /bin/bash ilkarg=$1 echo ilkarg ikinciarg=$2 echo ikinciarg arghepsi=$* echo arghepsi argsayisi=$# echo argsayisi
——————————————————————————–
komut satırından şu şeklide koşulursa ./ikinci_Bscript bir iki üç 4 falan
———–Çıktı ————————————————————–
bir
iki
bir iki üç 4 falan
5
——————————————————————————
Şimdi en baştakşi örneğin ne iş yaptığını anlayacak kadar biliyoruz :)
#!/bin/bash [ -z $1 ] && $0 1 && exit echo -n `$0 ((1+$1*2+2))` $#
“[ -z $1 ] && $0 1 && exit” buradaki && operatörünü açılamak gerekiyor tabi , Unix sistemlerde her komutun bir return değeri vardır. Komut başarıyla icra edilirse return değeri 0 , diğer durumlarda 0’dan farklı bir değer geriye döndürülür. Bu değer $? değişkeninde tutulur ve son icra edilen komutun başarılı olup olmadığını bu değişkeni okuyarak anlayabilirsiniz. Peki bu bize ne katacak? Eğer bir komutun icrasını kendinden önceki bir yada daha fazla komutun icrasının başarılı olup olmamasına bağlı olarak yapmak istiyorsanız && ve || operatörleri ile kullanabilirsiniz.
Örneğin ; ( ls && pwd ) gibi bir komut koştuğumuzu varsayalım. ls doğru koşarsa yani hata oluşmazsa yani return değeri 0 olursa pwd koşulur.
Sonuç olarak && operatörünün solundaki komut yada ifadenin succesfull(başarılı) olmasına veya return değerine bakılarak sağdaki ifade koşuluyor diyebiliriz.
[ -z $1 ] ifadesinde kapalı parantezleri bir if yapısı gibi düşünebilirsiniz. -z parametresi scriptin birinci argümanının ($1’in) zero yani sıfıra(var olup olmadığına) eşit olup olmadığını kontrol ediyor. Eğer sıfırsa yada argüman yoksa bu köşeli parantezli ifadenin return değeri 0 olacak ve && ‘in sağ tarafı koşulacaktır. ” $0 1″ aslında işi koparan nokta, $0 : bildiğimiz gibi scriptin kendi adı ve script 1 parametresiyle tekrar çağrılmış. Yani rekürsif bir şekilde devam edecek bundan sonrası.Yeni çağrılan processte scriptin ilk argümanı 1 olduğu için ilk satır koşulmayacak ikinci satır koşulacak.İkinci satırda da yine scriptin kendisini çağırdığına dikkat edin. Bu ifadenin verdiği çıktı şu şekilde olacak; 1 4 9 16 25 36 49 … sonsuza kadar devam edecek. Burada önemli olan rekürsiflik.
Shell scriptler, if, for, while ve switch case yapılarını destekler. Örneğin C dilinde “{ }” kıvırcık parantezleri bir ifadenin sınırlarını koymak için yardımcı oluyordu. Peki bu dilde hem satır girdileme hemde kıvırcık parantez olmadığına göre ifadeler nasıl sonlandırılıyor? Bütün ifadelerin scop’unu(bölgesini) belirleyen kendine özel sonlandırıcıları mevcut. Örneğin if ifadesi fi ile sonlanıyorken case ifadesi esac ile sonlanıyor.
if [koşul]; then
komutlarınız….
else
komutlarınız….
fi
örn:
if [$1 -gt $2 ] ;then echo $2 daha büyüktür $1 else echo $1 daha bbüyüktür $2 fi
for değişken in sequence(dizi)
do
komutlarınız….
done
Örn:
for i in `seq 0 10` do echo $i done
while koşul
do
komutlarınız…
done
Örn:
var=0 while [ $var -lt 10 ] do echo -n $var let "var += 1" done
case $variable in
1) komut1;;
2) komut2;;
3) komut3;;
*) diğer durumlarda ;;
esac
Örn:
echo -n "işlemi seçiniz(+,-,x,/): " read op case $op in +) echo `expr $1 + $2`;; -) echo `expr $1 - $2`;; x) echo `expr $1 * $2`;; /) echo `expr $1 / $2`;; *) echo "lütfen geçerli bir işlem seçiniz..." esac
NOT : expr aritmetik işlemlerinizi yapabilen bir komuttur. Mesela “expr 5 + 4″ çıktısı 9 dur.
NOT : back quote yani ters tırnak işareti ` ` bir komutu çalıştırmak için kullanılır. Örneğin “`gcc -o codeobj code.c`” gcc bildiğimiz gibi gnu c compiler’dır. Yani c derleyicisi…code.c kodunu derleyerek codeobj isimli executable file oluşturur.Yani script içerisinde gcc komutu gibi komutları çalıştırmak için back quote’lardan yararlanmak mümkün. Biz mesela yukarıdaki örnekte expr komutunu çalıştırdık.
Örneğin bir dosya içerisinde tutulan sayıları sıralamamız gereksin ve bunun için bir script yazmak isteyelim.
Bu sıralamayı en basit algoritmalardan biri olan bubble sort algoritmasıyla gerçekleştirmek istersek, shell komutları yardımıyla dosyayı istediğimiz gibi manipule edebiliriz.
#!/bin/bash file=$1 max=`cat $file | wc -l` declare -a arr var=0 for num in `cat $file` do arr[$var]=$num let "var += 1" done echo ----------sıralanmamış hali------------ echo ${arr[*]} echo --------sıralanmış hali --------------- for var1 in `seq 0 $max` do j=0 for var2 in `seq 0 $max` do index=`expr $j + 1` temp=${arr[$index]} swap=${arr[$j]} (( swap >= temp )) if [ $? -eq 0 ] then arr[$j]=${arr[$index]} arr[$index]=$swap fi let "j += 1" done done echo ${arr[*]}
—————ÇIKTI ————————————
———-sıralanmamış hali———-
1 5 10 11 12 18 17 3 4 6
———-sıralanmış hali————
1 3 4 5 6 10 11 12 17 18
———————————————————-
Muhtemelen bu kodun C dilindeki eşdeğeri daha az satırda yazılabilirdi, belki daha kısa bir şekilde bash script olarakta yazılabilirdi ama benim şu an aklıma gelen şekli bu.
Neyse olayı kısaca özetlemek gerekirse; $1 yani scriptin ilk argümanı olan dosya cat ile açılıp, wc -l ile kaç satır olduğuna bakılıyor, sebebi aşağıdaki iterasyon sayısını belirlemek. arr ismindeki diziye dosyadan okunan değerler atanıyor.
Ve klasik bubble sort algaritmasına göre içi içe iki for döngüsüyle dizide bir takım swap işlemleri yapılıyor. En sonda ise arr dizisinin yeni hali echo yardımıyla ekrana basılıyor.
Şimdi bash’in en sevdiğim kısmına geçelim, istediğimiz komutu çalıştırabiliyorsak yapabileceklerimizin sınırı yok. Ve bizde yine basit bir örnekle bu yazıyı noktalayalım.
Bildiğimiz şey şu kaynak dosyalarını derleyebilir, ve istediğimiz executable bir dosyayı çalıştırabiliriz.
Mesela tek satırda yazılan ve C dilinin kabul edeceği bir ifadeyi çalıştıran script yazalım.
———————Cinterpreter.sh————————————————————————-
#!/bin/bash while : do while [ -z "$stm" ] do echo -n " ?-> " read stm done [ "$stm" = "exit" ] && exit src=file$$.c exe=file$$ echo "#include<stdio.h>" > $src echo "int main() {" >> $src echo " $stm" >> $src echo "}">> $src gcc -o $exe $src > /dev/null 2>&1 if [ $? -ne 0 ] then echo "hatali girdi.. c dilinin kabul edebileceği bir ifade yazınız.." rm $src else ./$exe echo rm $src $exe fi unset stm done
———————-Çıktı——————————————————-
./Cinterpreter
?-> printf(“Hello World”);
Hello world
?->
———————————————————————————-
Daha fazla bilgi almak için buradan ve buradan faydalanabilirsiniz.