Bash dersi¶
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çıklama satırı ve dosya başlangıcı¶
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ı yazalım¶
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
Parametreler¶
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¶
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¶
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 alma¶
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¶
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 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üler¶
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¶
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
Dosya işlemleri¶
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.
Boru hattı¶
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
Kod bloğu¶
{ 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 komutu¶
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.
Birden çok dosya ile çalışmak¶
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¶
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.
fd kavramı¶
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.
Hata ayıklama¶
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