Bu yazıda bash betiği yazmayı hızlıca anlatacağım. Bu yazıda karıştırılmaması için girdilerin olduğu satırlar <- ile çıktıların olduğu satırlar -> ile işaretlenmiştir.
Açıklamalar # ifadesiden başlayıp satır sonuna kadar devam eder. Dosyanın ilk satırına #!/bin/bash eklememiz gerekmektedir. Bash betikleri genellikle .sh uzantılı olur. Bash betikleri girintilemeye duyarlı değildir. Bash betiği yazarken girintileme için 4 boşluk veya tek tab kullanmanızı öneririm.
Bash betiklerinde alt satıra geçmek yerine ; kullanabiliriz. Bu sayede kaynak kod daha düzenli tutulabilir.
#!/bin/bash #Bu bir açıklama satırıdır.
Bash betiklerini çalıştırmak için öncelikle çalıştırılabilir yapmalı ve sonrasında aşağıdaki gibi çalıştırılmalıdır.
chmod +x ders1.sh ./ders1.sh
: komutu hiçbir iş yapmayan komuttur. Bu komutu açıklama niyetine kullanabilirsiniz. true komutu ile aynı anlama gelmektedir.
Çoklu açımlama satırı için aşağıdaki gibi bir ifade kullanabilirsiniz.
: " Bu bir açıklama satıdırıdır. Bu da diğer açıklama satırıdır. Bu da sonunca açıklama satırıdır. "
Ekrana yazı yazmak için echo ifadesi kulanılır.
echo Merhaba dünya -> Merhaba dünya
Ekrana özel karakterleri yazmak için -e parametresi kullanmamız gerekmektedir.
echo -e "Merhaba\ndünya" -> Merhaba -> dünya
Ekrana renkli yazı da yazdırabiliriz. Bunun için \033[x;..;ym ifadesini kullanırız. Burada x ve y özellik belirtir. Örneğin:
# Mavi renkli kalın merhaba ile normal dünya yazdırır. echo -e "\033[34;1mMerhaba\033[0m Dünya"
Aşağıda tablo halinde özellik numarası ve anlamları verilmiştir.
Özellik | Anlamı | Özellik | Anlamı | Özellik | Anlamı |
---|---|---|---|---|---|
0 | Sıfırla | 30 | Siyah yazı | 40 | Siyah arka plan |
1 | Aydınlık | 31 | Kırmızı yazı | 41 | Kırmızı arka plan |
2 | Sönük | 32 | Yeşil yazı | 42 | Yeşil arka plan |
3 | İtalik | 33 | Sarı yazı | 43 | Sarı arka plan |
4 | Altı çizili | 34 | Mavi yazı | 44 | Mavi arka plan |
5 | Yanıp sönen | 35 | Magenta yazı | 45 | Magenta arkaplan |
6 | Yanıp sönen | 36 | Turkuaz yazı | 46 | Turkuaz arka plan |
7 | Ters çevirilmiş | 37 | Beyaz yazı | 47 | Beyaz arka plan |
8 | Gizli | 39 | Varsayılan yazı | 49 | Varsayılan arkaplan |
Çift tırnak (") içine yazılmış yazılardaki değişkenler işlenirken tek tırnak (') içindekiler işlenmez. Örneğin:
var=12 echo "$var" echo '$var' -> 12 -> $var
Bir bash betiği çalıştırılırken verilen parametreleri $ ifadesinden sonra gelen sayı ile kullanabiliriz. $# bize kaç tane parametre olduğunu verir. $@ ifadesi ile de parametrelerin toplamını elde edebiliriz.
echo "$1 - $# - $@" ./ders1.sh merhaba dünya -> merhaba - 2 - merhaba dünya
Değişkenler ve sabitler programımızın içerisinde kullanılan verilerdir. Değişkenler tanımlandıktan sonra değiştirilebilirken sabitler tanımlandıktan sonra değiştirilemez.
Değişkenler sayı ile başlayamaz, Türkçe karakter içeremez ve /*[[($ gibi özel karakterleri içeremez.
Normal Değişkenler aşağıdaki gibi tanımlanır.
sayı=23 yazi="merhaba"
+= ifadesi var olan değişkene ekleme yapmak için kullanılır. Değişkenin türünü belirlemeden tanımlamışsak yazı olarak ele alır.
typeset -i a # Değişkeni sayı olarak belirttik. a=1 ; b=1 a+=1 ; b+=1 echo "$a $b" -> 2 11
Çevresel değişkenler tüm alt programlarda da geçerlidir. Çevresel değişken tanımlamak için başına export ifadesi yerleştirilir.
export sayi=23 export yazi="merhaba"
Sabitler daha sonradan değeri değiştirilemeyen verilerdir. Sabit tanımlamak için başına decrale -r ifadesi yerleştirilir.
declare -r yazi="merhaba" declade -r sayi=23
Değişkenler ve sabitler kullanılırken ${} işareti içine alınırlar veya başına $ işareti gelir. Bu doküman boyunca ilk kullanım biçimi üzerinden gideceğim.
deneme="abc123" echo ${deneme} -> abc123
Sayı ve yazı türünden değişkenler farklıdır. sayıyı yazıya çevirmek için " işaretleri arasına alabiliriz. Birden fazla yazıyı toplamak için yan yana yazmamız yeterlidir.
sayi=11 yazi="karpuz" echo "${sayi}${karpuz} limon" -> 11karpuz limon
Sayı değişkenleri üzerinde matematiksel işlem yapmak için aşağıdaki ifade kullanılır. (+-*/ işlemleri için geçerlidir.)
sayi=12 sayi=$((${sayi}/2)) echo ${sayi} -> 6
Bununla birlikte matematiksel işlemler için şunlar da kullanılabilir.
expr 3 + 5 # her piri arasında boşluk gerekli -> 8 echo 6-1 | bc -l # Burada -l virgüllü sayılar için kullanılır. -> 5 python3 -c "print(10/2)" -> 5.0
Değişkenlere aşağıdaki tabloda belirttiğim gibi müdahale edilebilir. Karakter sayısı 0'dan başlar. Negatif değerler sondan saymaya başlar.
İfade | Anlamı | Eşleniği |
---|---|---|
${var%aba} | Sondaki ifadeyi sil | Merh |
${var#Mer} | Baştaki ifadeyi sil | haba |
${var:1:4} | Baştan 1. 4. karakterler arası | erha |
${var::4} | Baştan 4. karaktere kadar | Merha |
${var:4} | Baştan 4. karakterden sonrası | aba |
${var/erh/abc} | erh yerine abc koy | Mabcaba |
${var,,} | hepsini küçük harf yap | merhaba |
${var^^} | hepsini büyük harf yap | MERHABA |
${var:+abc} | var tanımlıysı abc dördürür. | abc |
Diziler birden çok eleman içeren değişkenlerdir. Bash betiklerinde diziler aşağıdaki gibi tanımların ve kullanılır.
dizi=(muz elma limon armut) echo ${dizi[1]} -> elma echo ${#dizi[@]} -> 4 echo ${dizi[@]:2:4} -> limon armut dizi+=(kiraz) echo ${dizi[-1]} -> kiraz
Diziler eleman indisleri ile kullanmanın yanında şu şekilde de tanımlanabilir.
declare -A dizi dizi=([kirmizi]=elma [sari]=muz [yesil]=limon [turuncu]=portakal) for isim in ${!dizi[@]} ; do echo -n "$isim " done echo -> turuncu yesil sari kirmizi for isim in ${dizi[@]} ; do echo -n "$isim " done echo -> portakal limon muz elma echo ${dizi[kirmizi]} -> elma
Klavyeden değer almak için read komutu kullanılır. Alınan değer değişken olarak tanımlanır.
read deger <- merhaba echo $deger -> merhaba
Koşullar if ile fi ile biter. Koşul ifadesi sonrası then kullanılır. ilk koşul sağlanmıyorsa elif ifadesi ile ikinci koşul sorgulanabilir. Eğer hiçbir koşul sağlanmıyorsa else ifadesi içerisindeki eylem gerçekleştirilir.
if ifade ; then eylem elif ifade ; then eylem else eylem fi
Koşul ifadeleri kısmında çalıştırılan komut 0 döndürüyorsa doğru döndürmüyorsa yalnış olarak değerlendirilir. [[ veya [ ile büyük-küçük-eşit kıyaslaması, dosya veya dizin varlığı vb. gibi sorgulamalar yapılabilir. Bu yazıda [[ kullanılacaktır.
read veri if [[ ${veri} -lt 10 ]] ; then echo "Veri 10'dan küçük" else echo "Veri 10'dan büyük veya 10a eşit" fi <- 9 -> Veri 10'dan küçük <- 15 -> Veri 10'dan büyük veya 10a eşit
[[ yerleşiği ile ilgili başlıca ifadeleri ve kullanımlarını aşağıda tablo olarak ifade ettim. [ bir komutken [[ bir yerleşiktir. [ ayrı bir süreç olarak çalıştırılır. Bu yüzden [[ kullanmanızı tavsiye ederim.
İfade | Anlamı | Kullanım şekli |
---|---|---|
-lt | küçüktür | [[ ${a} -lt 5 ]] |
-gt | büyüktür | [[ ${a} -gt 5 ]] |
-eq | eşittir | [[ ${a} -eq 5 ]] |
-le | küçük eşittir | [[ ${a} -le 5 ]] |
-ge | büyük eşittir | [[ ${a} -ge 5 ]] |
-f | dosyadır | [[ -f /etc/os-release ]] |
-d | dizindir | [[ -d /etc ]] |
-e | vardır (dosya veya dizindir) | [[ -e /bin/bash ]] |
-L | sembolik bağdır | [[ -L /lib ]] |
-n | uzunluğu 0 değildir | [[ -n ${a} ]] |
-z | uzunluğu 0dır | [[ -z ${a} ]] |
! | ifadenin tersini alır. | [[ ! .... veya ! [[ .... |
> | alfabeti olarak büyüktür | [[ "portakal" > "elma" ]] |
< | alfabetik olarak küçüktür | [[ "elma" < "limon" ]] |
== | alfabetik eşittir | [[ "nane" == "nane" ]] |
!= | alfabetik eşit değildir | [[ "name" != "limon" ]] |
=~ | regex kuralına göre eşittir | [[ "elma1" =~ ^[a-z]l.*[1]$ ]] |
|| | mantıksal veya bağlacı | [[ .... || .... ]] veya [[ .... ]] || [[ .... ]] |
&& | mantıksal ve bağlacı | [[ .... && .... ]] veya [[ .... ]] && [[ .... ]] |
true komutu her zaman doğru false komutu ile her zaman yanlış çıkış verir.
Bazı basit koşul ifadeleri için if ifadesi yerine aşağıdaki gibi kullanım yapılabilir.
[[ 12 -eq ${a} ]] && echo "12ye eşit." || echo "12ye eşit değil" #bunun ile aynı anlama gelir: if [[ 12 -eq ${a} ]] ; then echo "12ye eşit" else echo "12ye eşit değil" fi
case yapısı case ile başlar değerden sonra gelen in ile devam eder ve koşullardan sonra gelen esac ile tamamlanır. case yapısı sayesinde if elif else ile yazmamız gereken uzun ifadeleri kısaltabiliriz.
case deger in elma | kiraz) echo "meyve" ;; patates | soğan) echo "sebze" ;; balık) echo "hayvan" *) echo "hiçbiri" ;; esac # Şununla aynıdır: if [[ "${deger}" == "elma" || "${deger}" == "kiraz" ]] ; then echo "meyve" elif [[ "${deger}" == "patates" || "${deger}" == "soğan" ]] ; then echo "sebze" elif [[ "${değer}" == "balık" ]] ; then echo "hayvan" else echo "hiçbiri" fi
Döngülerde while ifadesi sonrası koşul gelir. do ile devam eder ve eylemden sonra done ifadesi ile biter. Döngülerde ifade doğru olduğu sürece eylem sürekli olarak tekrar eder.
while ifade ; do eylem done
Örneğin 1den 10a kadar sayıları ekrana yan yana yazdıralım. Eğer echo komutumuzda -n parametresi verilirse alt satıra geçmeden yazmaya devam eder.
i=1 while [[ ${i} -le 10 ]] ; do echo -n "$i " # sayıyı yazıya çevirip sonuna yanına boşluk koyduk i=$((${i}+1)) # sayıya 1 ekledik done echo # en son alt satıra geçmesi için -> 1 2 3 4 5 6 7 8 9 10
for ifadesinde değişken adından sonra in kullanılır daha sonra dizi yer alır. diziden sonra do ve bitişte de done kullanılır.
for degisken in dizi ; do eylem done
Ayrı örneğin for ile yapılmış hali
for i in 1 2 3 4 5 6 7 8 9 10 ; do echo -n "${i} " done echo -> 1 2 3 4 5 6 7 8 9 10
Ayrıca uzun uzun 1den 10a kadar yazmak yerine şu şekilde de yapabiliyoruz.
for i in {1..10} ; do echo -n "${i} " done echo -> 1 2 3 4 5 6 7 8 9 10
Buradaki özel kullanımları aşağıda tablo halinde belirttim.
İfade | Anlamı | eşleniği |
---|---|---|
{1..5} | aralık belirtir | 1 2 3 4 5 |
{1..7..2} | adımlı aralık belirtir | 1 3 5 7 |
{a,ve}li | kurala uygun küme belirtir | ali veli |
Fonksiyonlar alt programları oluşturur ve çağırıldığında işlerini yaptıktan sonra tekrar ana programdan devam edilmesini sağlar. Bir fonksiyonu aşağıdaki gibi tanımlayabiliriz.
isim(){ eylem return sonuç } # veya function isim(){ eylem return sonuç }
Burada return ifadesi kullanılmadığı durumlarda 0 döndürülür. return ifadesinden sonra fonksiyon tamamlanır ve ana programdan devam edilir.
Bu yazı boyunca ilkini tercih edeceğiz.
Fonksionlar sıradan komutlar gibi parametre alabilirler ve ana programa ait sabit ve değişkenleri kullanabilirler.
sayi=12 topla(){ echo $((${sayi}+$1)) return 0 echo "Bu satır çalışmaz" } topla 1 -> 13
local ifadesi sadece fonksionun içinde tanımlanan fonksion bitiminde silinen değişkenler için kullanılır.
Fonksionların çıkış turumlarını koşul ifadesi yerine kullanabiliriz.
read sayi teksayi(){ local i=$(($1+1)) # sayıya 1 ekledik ve yerel hale getirdik. return $((${i}%2)) # sayının 2 ile bölümünden kalanı döndürdük } if teksayi ${sayi} ; then echo "tek sayıdır" else echo "çift sayıdır" fi <- 12 -> çift sayıdır <- 5 -> tek sayıdır
Bir fonksionun çıktısını değişkene $(isim) ifadesi yadımı ile atayabiliriz. Aynı durum komutlar için de geçerlidir.
yaz(){ echo "Merhaba" } echo "$(yaz) dünya" -> Merhaba dünya
Tanımlı bir fonksionu silmek için unset -f ifadesini kullanmamız gereklidir.
yaz(){ echo "Merhaba" } unset -f yaz echo "$(yaz) dünya" -> bash: yaz: komut yok -> dünya
Burada dikkat ederseniz olmayan fonksionu çalıştırmaya çalıştığımız için hata mesajı verdi fakat çalışmaya devam etti. Eğer herhangi bir hata durumunda betiğin durmasını istiyorsak set -e bu durumun tam tersi için set +e ifadesini kullanmalıyız.
echo "satır 1" acho "satır 2" # yanlış yazılan satır fakat devam edecek echo "satır 3" set -e acho "satır 4" # yanlış yazılan satır çalışmayı durduracak echo "satır 5" # bu satır çalışmayacak -> satır 1 -> bash: acho: komut yok -> satır 3 -> bash: acho: komut yok
Bash betiklerinde stdout stderr ve stdin olmak üzere 2 çıktı ve 1 girdi bulunur. Ekrana stderr ve stdout beraber yazılır.
İfade | Türü | Anlamı |
---|---|---|
stdin | Girdi | Klavyeden girilen değerler. |
stdout | Çıktı | Sıradan çıktılardır. |
stderr | Çıktı | Hata çıktılarıdır. |
cat komutu ile dosya içeriğini ekrana yazdırabiliriz. Dosya içeriğini $(cat dosya.txt) kullanarak değişkene atabiliriz.
dosya.txt içeriğinin aşağıdaki gibi olduğunu varsayalım.
Merhaba dünya Selam dünya sayı:123
Ayağıdaki örnekle dosya içeriğini önce değişkene atayıp sonra değişkeni ekrana yazdırdık.
icerik=$(cat ./dosya.txt) echo "${icerik}" -> Merhaba dünya -> Selam dünya -> sayı:123
grep "sözcük" dosya.txt ile dosya içerisinde sözcük gezen satırları filitreleyebiliriz. Eğer grep komutuna -v paraketresi eklersek sadece içermeyenleri filitreler. Eğer filitrelemede hiçbir satır bulunmuyorsa yanlış döner.
grep "dünya" dosya.txt -> Merhaba dünya -> Selam dünya grep -v "dünya" dosya.txt -> sayi:123
Aşağıdaki tabloda bazı dosya işlemi ifadeleri ve anlamları verilmiştir.
İfade | Anlamı | Kullanım şekli |
---|---|---|
> | çıktıyı dosyaya yönlendir (stdout) | echo "Merhaba dünya" > dosya.txt |
2> | çıktıyı dosyaya yönlendir (stderr) | ls /olmayan/dizin 2> dosya.txt |
>> | çıktıyı dosyaya ekle | echo -n "Merhaba" > dosya.txt && echo "dünya" >> dosya.txt |
&> | çıktıyı yönlendir (stdout ve stderr) | echo "$(cat /olmayan/dosya) deneme" &> dosya.txt |
Ayrıca dosyadan veri girişleri için de aşağıda örnekler verilmiştir:
# <<EOF: # EOF ifadesi gelene kadar olan kısmı girdi olarak kullanır: cat > dosya.txt <<EOF Merhaba dünya EOF # < dosya.txt # Bir dosyayı girdi olarak kullanır: while read line ; do echo ${line:2:5} done < dosya.txt
/dev/null içine atılan çıktılar yok edilir. /dev/stderr içine atılan çıktılar ise hata çıktısı olur.
Bash betiklerinde stdin yerine bir önceki komutun çıktısını kullanmak için boru hattı açabiliriz. Boru hattı açmak için iki komutun arasına | işareti koyulur. Boru hattında soldan sağa doğru çıktı akışı vardır. Boru hattından sadece stdout çıktısı geçmektedir. Eğer stderr çıktısını da boru hattından geçirmek istiyorsanız |& kullanmalısınız.
topla(){ read sayi1 read sayi2 echo $((${sayi1}+${sayi2})) } topla <- 12 <- 25 -> 37 sayiyaz(){ echo 12 echo 25 } sayiyaz | topla -> 37
{ ile } arasına yazılan kodlar birer kod bloğudur. Kod blokları fonksionların aksine argument almazlar ve bir isme sahip değillerdir. Kod blokları tanımlandığı yerde çalıştırılırlar. Kod bloğuna boru hattı ile veri girişi ve çıkışı yapılabilir.
cikart(){ read sayi1 read sayi2 echo $((${sayi1}-${sayi2})) } cikart <- 25 <- 12 -> 13 { echo 25 echo 12 } | cikart -> 13 # veya kısaca şu şekilde de yapılabilir. { echo 25 ; echo 12 ; } | cikart -> 13
select kullanarak basit menü oluşturabiliriz.
select deger in ali veli 49 59 ; do echo $REPLY # seçilen sayıyı verir echo $deger # seçilen elemanı verir break done -> 1) ali -> 2) veli -> 3) 49 -> 4) 59 -> #? <- 1 -> 1 -> ali
Bu örnekde REPLY değişkeni seçtiğimiz sayıyı deger değişkeni ise seçtiğimiz elemanı ifade eder. select komutu sürekli olarak döngü halinde çalışır. Döngüden çıkmak için break kullandık.
Bash betikleri içerisinde diğer bash betiği dosyasını kullanmak için source yada . ifadeleri kullanılır. Diğer betik eklendiği zaman içerisinde tanımlanmış olan değişkenler ve fonksionlar kullanılabilir olur.
Örneğin deneme.sh dosyamızın içeriği aşağıdaki gibi olsun:
mesaj="Selam" merhaba(){ echo ${mesaj} } echo "deneme yüklendi"
Asıl betiğimizin içeriği de aşağıdaki gibi olsun.
source deneme.sh # deneme.sh dosyası çalıştırılır. merhaba -> deneme yüklendi -> Selam
Ayrıca bir komutun çıktısını da betiğe eklemek mümkündür. Bunun için <(komut) ifadesi kullanılır. Aşağıda bununla ilgili bir örnek verilmiştir.
source <(curl https://gitlab.com/sulincix/outher/-/raw/gh-pages/deneme.sh) # Örnekteki adrese takılmayın :D merhaba merhaba2 echo ${sayi} -> Merhaba dünya -> 50 -> 100
exec komutu betiğin bundan sonraki bölümünü çalıştırmak yerine hedefteki komut ile değiştirilmesini sağlar. exec ile çalıştırılmış olan komut tamamlandığında betik tamamlanmış olur.
echo $$ # pid değeri yazdırır bash -c 'echo $$' # yeni süreç oluşturduğu için pid değeri farklıdır. exec bash -c 'echo $$' # mevcut komut ile değiştirildiği için pid değeri değişmez echo "hmm" # Bu kısım çalıştırılamaz. -> 5755 -> 5756 -> 5755
exec komutunu doğrudan terminalde çalıştırırsanız ve komut tamamlanırsa terminaldeki süreç kapanacağı için terminal doğal olarak kapanacaktır.
exec komutunu kullanarak yönlendirmeler yapabilirsiniz.
exec > log.txt # bütün çıktıları log.txt içine yazdırır. echo "merhaba" # ekrana değil dosyaya yazılır. exec < komutlar.txt # komutlar.txt dosyasındakiler girdi olarak kullanılır.
bash programında birden çok fd kullanılabilir. var olan fdlere ulaşmak için /proc/$$/fd/ konumuna bakabiliriz. 0 stdin 1 stdout 2 stderr olarak çalışır.
Not: $$ mevcut sürecin pid değerini verir.
exec 3> log.txt # yazmak için boş bir 3 numaralı fd açmak için. echo "deneme" >&3 exec 3>& - # açık olan 3 nuramalı fd kapatmak için. exec 2>&1 # stderr içine atılanı stdout içine aktarır. exec 4< input.txt # okumak için 4 numaralı fd açmak için. echo "hmm" > input.txt # girdi dosyamıza yazı yazalım. read line <&4 # 3 nuramalı fd içinden değir okur. exec 4<&- # 4 numaralı fd kapatmak için.
bash komutuna farklı parametreler vererek kolayca script'inizi derleyebilirsiniz. Örneğin -n parametresi kodu çalıştırmayıp sadece hata kontrolü yapacaktır, -v komutları çalıştırmadan yazdıracak, -x ise işlem bittikten sonra kodları yazdıracaktır.
bash -n script_adi.sh bash -v script_adi.sh bash -x script_adi.sh