Bash'te if komutu bir koşulun doğru olup olmadığına değil, bir komutun çıkış durumuna bakar: if ile then arasındaki komut 0 ile biterse then bloğu çalışır, sıfırdan farklı bir değerle biterse varsa elif dalları sırayla denenir, hiçbiri tutmazsa else bloğu çalışır. Blok fi ile kapanır. Yani [ ... ] ya da [[ ... ]] sözdiziminin bir parçası değildir; onlar da çıkış durumu üreten sıradan komutlardır.

Bu yazıda if, elif ve else yapısının tam sözdizimini, koşul olarak herhangi bir komutu nasıl kullanacağınızı, ! ile tersleme yapmayı, tek satırlık yazımı ve yeni başlayanların en sık yaptığı hataları örneklerle göreceksiniz. Koşul içinde kullanılan test ifadelerinin (-f, -z, -eq gibi) ayrıntıları ayrı bir yazının konusu: test ve [[ ]] farkı. Bütün örnekler GNU Bash 5.2.37 ile çalıştırıldı ve çıktı yorumları gerçek çıktıdır.

if komutunun sözdizimi

Kılavuzdaki genel biçim şudur: önce if ve bir komut listesi, ardından then ve koşul doğruysa çalışacak komutlar gelir. İsterseniz istediğiniz kadar elif liste; then komutlar dalı ve en fazla bir tane else komutlar dalı ekleyebilirsiniz. Her şeyin sonunda fi yazılır; fi, if kelimesinin tersten yazılmış halidir ve unutulması en sık görülen sözdizimi hatalarından biridir.

if, then, elif, else ve fi Bash'in ayrılmış kelimeleridir. Ayrılmış kelimeler yalnızca bir komutun ilk kelimesi olduklarında tanınır. Bu yüzden then kelimesini koşulla aynı satıra yazmak istiyorsanız araya noktalı virgül koymanız gerekir: if komut; then. Noktalı virgül yerine satır sonu da kullanılabilir; kılavuz, sözdiziminde ; gördüğünüz her yerde bir ya da daha fazla yeni satır kullanabileceğinizi söyler.

bash
#!/usr/bin/env bash
puan=72

if (( puan >= 85 )); then
  echo "Pekiyi"
elif (( puan >= 70 )); then
  echo "İyi"
elif (( puan >= 50 )); then
  echo "Geçer"
else
  echo "Kaldı"
fi
# çıktı: İyi

Burada koşul olarak sayısal karşılaştırma için (( ... )) kullanıldı. Dallar yukarıdan aşağıya denenir ve ilk doğru çıkan dalın komutları çalıştıktan sonra if tamamen biter. puan 72 olduğu için ikinci dal çalıştı; üçüncü dal da doğru olurdu ama hiç denenmedi. Bu yüzden aralık kontrollerinde koşulları en dar olandan en geniş olana doğru sıralamanız gerekir. Sıralamayı ters çevirip önce puan >= 50 kontrolünü yazsaydınız, 72 puan da 95 puan da "Geçer" sonucunu alırdı.

Koşul aslında bir komuttur

Bash'in çoğu programlama dilinden ayrıldığı nokta budur. Kılavuza göre if, kendisinden sonra gelen komut listesini çalıştırır ve listenin dönüş durumu sıfırsa then bölümüne geçer. Kabukta 0 başarı, 0 dışındaki her değer başarısızlık demektir. Dolayısıyla if sonrasına cd, read, source ya da yazdığınız bir fonksiyon gibi herhangi bir komutu koyabilirsiniz; ayrıca köşeli parantez açmanıza gerek yoktur.

bash
#!/usr/bin/env bash
if cd ./olmayan-dizin 2>/dev/null; then
  echo "dizine girildi"
else
  echo "dizin yok, devam ediliyor"
fi
# çıktı: dizin yok, devam ediliyor

kullanici_var_mi() {
  [[ $1 == "ayse" || $1 == "mehmet" ]]
}

if kullanici_var_mi "ayse"; then
  echo "ayse tanımlı"
fi
# çıktı: ayse tanımlı

İkinci bölümde kullanici_var_mi fonksiyonu hiçbir return yazmadan da işe yarıyor, çünkü bir fonksiyonun dönüş durumu son çalıştırdığı komutun durumudur; burada son komut [[ ... ]] testidir. Bu kalıp, karmaşık koşulları isimlendirilmiş küçük fonksiyonlara ayırmanın temiz bir yoludur. Fonksiyonların dönüş değeriyle ilgili ayrıntılar için Bash fonksiyon tanımlama yazısına bakabilirsiniz.

Koşul olarak tek komut yerine bir liste de verilebilir. Kılavuz bu kısmı test-commands listesi diye adlandırır ve kararın listedeki son komutun durumuna göre verildiğini belirtir. Örneğin if cd ./a; cd ./b; then yazarsanız yalnızca ikinci cd sonucu belirler; ilki başarısız olsa bile fark edilmez. Birden fazla koşulun hepsinin tutmasını istiyorsanız noktalı virgül yerine && kullanın; bu operatörlerin kuralları Bash && ve || operatörleri yazısında anlatılıyor.

Hangi koşul aracı ne zaman kullanılır?

Hangi koşul aracı ne zaman kullanılır? [ ifade ] veya test ifade, [[ ifade ]], (( ifade )), herhangi bir komut

Koşul yerine yazdığınız şey çoğu zaman şu dört araçtan biri olur. Hepsi bir çıkış durumu döndürür, if de yalnızca bu duruma bakar:

YazımNe yaparTipik kullanım
[ ifade ] veya test ifadePOSIX uyumlu test yerleşik komutush ile de çalışması gereken betikler
[[ ifade ]]Bash ayrılmış kelimesi; kelime bölme ve glob yapmaz, desen ve regex desteklerBash betiklerinde metin ve dosya testleri
(( ifade ))Aritmetik değerlendirme; sonuç 0 değilse durum 0 olurSayısal karşılaştırmalar
herhangi bir komutKomutun kendi çıkış durumucd, read, source, fonksiyonlar

Sayıları karşılaştırırken (( a > b )) yazımı genellikle en okunaklısıdır; aritmetik bağlamda değişkenlerin başına $ koymanız bile gerekmez. Ayrıntılar için Bash aritmetik işlemler yazısına bakın. Metin ve dosya testleri için Bash betiklerinde [[ ]] daha güvenlidir; nedenleri aşağıdaki hatalar bölümünde göreceksiniz.

! ile koşulu tersleme

! ile koşulu tersleme

Bir koşulun tutmadığı durumda bir şey yapmak istiyorsanız komutun önüne ! koyabilirsiniz. Kılavuza göre bir boru hattının başına ! konduğunda çıkış durumu mantıksal olarak terslenir: 0 olan 1 olur, 0 dışındaki değer 0 olur. ! de bir ayrılmış kelimedir; komuttan bir boşlukla ayrılması gerekir.

bash
#!/usr/bin/env bash
yapilandirma=./ayarlar.conf

if ! [[ -f $yapilandirma ]]; then
  echo "Ayar dosyası bulunamadı: $yapilandirma"
fi
# çıktı: Ayar dosyası bulunamadı: ./ayarlar.conf

false
# $? = 1
! false
# $? = 0

if ! [[ -f $dosya ]] ile if [[ ! -f $dosya ]] aynı sonucu verir. İlkinde komutun durumu terslenir, ikincisinde ! koşul ifadesinin içindeki bir operatördür. Okunabilirlik açısından ikisi de kabul görür; önemli olan bir betik içinde tutarlı olmaktır. Bir yan bilgi: ! ile terslenen komutlar set -e açık olsa bile betiği durdurmaz. Bu ve benzeri istisnalar set -euo pipefail yazısında ele alınıyor.

elif ile çoklu dallar ve iç içe if

elif dalları, else içine yeni bir if yazmanın kısaltmasıdır ve kod girintisinin sağa doğru kaymasını önler. Kılavuz akışı şöyle tarif eder: ilk koşul tutmazsa her elif listesi sırayla çalıştırılır; bunlardan biri sıfır döndürdüğünde ona ait komutlar çalışır ve komutun tamamı biter. Hiçbiri tutmadığında else varsa o çalışır.

İç içe if ise iki bağımsız soruyu art arda sormanız gerektiğinde mantıklıdır. Örneğin önce bir yolun var olup olmadığına, sonra var ise dosya mı dizin mi olduğuna bakmak istiyorsanız dış if varlığı, iç if türü kontrol edebilir. Yine de üç seviyeden derin yuvalama okunabilirliği hızla düşürür. Aynı değişkeni birçok sabit değerle karşılaştırıyorsanız elif zinciri yerine case esac yapısı çok daha temiz bir sonuç verir.

bash
#!/usr/bin/env bash
touch ./rapor.txt
mkdir -p ./arsiv

for yol in ./rapor.txt ./arsiv ./yok; do
  if [[ -e $yol ]]; then
    if [[ -d $yol ]]; then
      echo "$yol: dizin"
    else
      echo "$yol: dosya"
    fi
  else
    echo "$yol: bulunamadı"
  fi
done
# çıktı:
#   ./rapor.txt: dosya
#   ./arsiv: dizin
#   ./yok: bulunamadı

if komutunun kendi dönüş durumu

if bloğu da bir komuttur ve bir çıkış durumu bırakır. Kılavuza göre bu değer, blok içinde son çalıştırılan komutun durumudur; hiçbir koşul tutmadıysa ve else yoksa 0'dır. Bu ikinci kural şaşırtıcı gelebilir: koşulun başarısız olması, if komutunun başarısız olduğu anlamına gelmez. Hemen ardından $? değerine bakan bir kod yazıyorsanız bunu hesaba katın.

bash
#!/usr/bin/env bash
if false; then
  echo "çalışmaz"
fi
# $? = 0

if true; then
  false
fi
# $? = 1

Birinci blokta koşul tutmadı ve else yok, bu yüzden durum 0. İkinci blokta then içinde son çalışan komut false olduğu için durum 1. Çıkış kodlarının genel kuralları için Bash exit kodları yazısına göz atabilirsiniz.

Tek satırda if ve boş dal

Kısa kontrollerde bütün yapıyı tek satıra yazabilirsiniz; tek koşul, her ayrılmış kelimeden önce bir noktalı virgül ya da yeni satır bulunmasıdır: if [[ -f ./a ]]; then echo var; else echo yok; fi. Etkileşimli kabukta pratiktir ama betiklerde birden fazla komut içeren dalları çok satıra yaymak hem okumayı hem de hata ayıklamayı kolaylaştırır.

Bash, then ile fi arasının boş kalmasına izin vermez. Bir dalı geçici olarak boş bırakmak istiyorsanız hiçbir şey yapmayan ve her zaman 0 döndüren : yerleşik komutunu kullanın. Kılavuz : için yalnızca argümanlarını genişletip yönlendirmeleri yaptığını, başka hiçbir şey yapmadığını söyler.

bash
#!/usr/bin/env bash
mod="sessiz"
if [[ $mod == "sessiz" ]]; then
  :   # şimdilik bir şey yapma
else
  echo "ayrıntılı mod"
fi
echo "bitti"
# çıktı: bitti

: satırını silip dalı gerçekten boş bıraktığınızda betik hiç çalışmaz; Bash dosyayı okurken bir sözdizimi hatası verir. Aşağıda bu hatayı ve diğer sık görülen hataları tek tek görebilirsiniz.

Sık yapılan hatalar

Sık yapılan hatalar: Köşeli parantezin çevresinde boşluk unutmak, Değişkeni tırnaksız bırakmak, [ false ] ifadesinin doğru çıkması, fi ya da then unutmak

Köşeli parantezin çevresinde boşluk unutmak

[ bir komut adıdır ve ] onun son argümanıdır. Aralarındaki her parça ayrı bir kelime olmalıdır. [$x -gt 3] yazdığınızda Bash, $x genişledikten sonra [5 adında bir komut arar:

bash
#!/usr/bin/env bash
x=5
if [$x -gt 3]; then echo "büyük"; fi
# çıktı: ornek.sh: line 3: [5: command not found

Doğrusu if [ $x -gt 3 ] ya da daha iyisi if (( x > 3 )) biçimidir. Dikkat edin, hata mesajından sonra betik çalışmaya devam etti: "command not found" yalnızca koşulun başarısız sayılmasına yol açar, else olsaydı o çalışırdı. Bu tür sessiz hataları yakalamak için set -x ile hata ayıklama yazısındaki yöntemleri kullanabilirsiniz.

Değişkeni tırnaksız bırakmak

Tek köşeli parantez sıradan bir komut olduğu için içindeki tırnaksız değişkenler kelime bölmeye ve dosya adı genişletmesine uğrar. Değişken boşsa argüman sayısı azalır, içinde boşluk varsa artar. İki durumda da test, beklediğiniz soruyu sormaz:

bash
#!/usr/bin/env bash
secim=""
if [ $secim = "evet" ]; then echo "onaylandı"; fi
# çıktı: ornek.sh: line 3: [: =: unary operator expected

secim="belki evet"
if [ $secim = "evet" ]; then echo "onaylandı"; fi
# çıktı: ornek.sh: line 7: [: too many arguments

if [[ $secim == "evet" ]]; then echo "onaylandı"; else echo "onay yok"; fi
# çıktı: onay yok

Çözüm ya [ "$secim" = "evet" ] biçiminde her değişkeni çift tırnağa almak ya da kelime bölme yapmayan [[ ]] kullanmaktır. Tırnakların genel mantığını tek tırnak ve çift tırnak farkı yazısında bulabilirsiniz.

[ false ] ifadesinin doğru çıkması

if [ false ] yazan biri koşulun yanlış olmasını bekler, ama test komutu tek argüman aldığında yalnızca o argümanın boş olup olmadığına bakar. "false" boş olmayan bir metin olduğundan sonuç doğrudur. Aynı şekilde [ 0 ] da doğrudur. true ve false komutlarını koşul olarak kullanmak istiyorsanız köşeli parantez açmadan yazın: if false; then. Değişkende komut adı tutup if $durum; then yazmak da mümkündür ama değişken beklenmeyen bir değer alırsa Bash o değeri komut olarak çalıştırmayı dener; bu yüzden durum bayraklarını (( bayrak )) ya da [[ $bayrak == evet ]] ile kontrol etmek daha güvenlidir.

bash
#!/usr/bin/env bash
if [ false ]; then echo "doğru sayıldı"; fi
# çıktı: doğru sayıldı
if false; then echo "doğru sayıldı"; else echo "yanlış sayıldı"; fi
# çıktı: yanlış sayıldı

fi ya da then unutmak

Bash bir betiği çalıştırmadan önce her bileşik komutu bütün olarak ayrıştırır. fi eksikse dosyanın sonuna kadar kapanış arar ve bulamayınca "unexpected end of file" hatası verir; hata satırı olarak dosyanın son satırını gösterdiği için asıl eksik yeri yukarıda aramanız gerekir. then eksikse ya da boş bir dal bıraktıysanız "syntax error near unexpected token" mesajı ile beklenmeyen kelime gösterilir:

bash
$ cat eksik.sh
if true; then
fi
$ bash eksik.sh
eksik.sh: line 2: syntax error near unexpected token `fi'
eksik.sh: line 2: `fi'

Bu mesajlar ve başka yaygın hata metinleri Bash sık hata mesajları yazısında ayrıca açıklanıyor.

Küçük bir uygulama: argüman ve dosya kontrolü

Şimdi öğrendiklerinizi birleştirelim. Aşağıdaki betik bir yedekleme aracının giriş kısmını taklit ediyor: argüman verilmemişse kullanım bilgisi yazar, verilen yol yoksa uyarır, dizinse ve boş değilse işleme devam eder. Örneği kendi içinde çalışır kılmak için set -- ile argümanları betiğin içinde belirledik; gerçek bir betikte bu değerler komut satırından gelirdi. Argümanların ayrıntıları için Bash betik argümanları yazısına bakabilirsiniz.

bash
#!/usr/bin/env bash
kontrol_et() {
  local hedef=$1
  if [[ -z $hedef ]]; then
    echo "kullanım: yedekle DİZİN"
    return 2
  elif [[ ! -e $hedef ]]; then
    echo "hata: $hedef bulunamadı"
    return 1
  elif [[ ! -d $hedef ]]; then
    echo "hata: $hedef bir dizin değil"
    return 1
  fi
  echo "tamam: $hedef yedeklenecek"
}

mkdir -p ./veriler
touch ./not.txt
kontrol_et ""
kontrol_et ./yok
kontrol_et ./not.txt
kontrol_et ./veriler
# çıktı:
#   kullanım: yedekle DİZİN
#   hata: ./yok bulunamadı
#   hata: ./not.txt bir dizin değil
#   tamam: ./veriler yedeklenecek

Bu yapı "erken çıkış" (guard clause) olarak bilinen bir düzeni izler: hatalı durumlar önce elenir, ana iş en sonda ve girintisiz kalır. Fonksiyon yerine doğrudan betikte yazıyorsanız return yerine exit kullanırsınız. Farklı hata türleri için farklı çıkış kodları döndürmek (burada kullanım hatası için 2, diğerleri için 1), betiğinizi çağıran başka bir betiğin ya da cron görevinin neyin yanlış gittiğini anlamasını kolaylaştırır.

Sık Sorulan Sorular

Bash if içinde neden köşeli parantez kullanılıyor?

Köşeli parantez if sözdiziminin parçası değildir. [, test komutunun başka bir adıdır ve bir koşul ifadesini değerlendirip 0 ya da 1 döndürür. if bu sonucu kullanır. Bu yüzden köşeli parantez olmadan da if cd ./dizin; then gibi koşullar yazabilirsiniz.

if satırının sonuna noktalı virgül koymak zorunlu mu?

then kelimesini koşulla aynı satıra yazıyorsanız evet. Ayrılmış kelimeler yalnızca bir komutun başında tanındığı için koşul komutunun noktalı virgül ya da yeni satırla bitmesi gerekir. then kelimesini bir alt satıra yazarsanız noktalı virgüle gerek kalmaz.

Birden fazla koşulu nasıl birleştiririm?

[[ ]] içinde && ve || kullanabilirsiniz: [[ -f $a && -r $a ]]. Komut düzeyinde de if [ -f "$a" ] && [ -r "$a" ]; then yazılabilir. Kılavuz, eski -a ve -o operatörlerinin POSIX tarafından kullanımdan kaldırıldığını belirtir, yeni betiklerde bunları kullanmayın.

Bash if içinde sayı mı metin mi karşılaştırdığımı nasıl anlar?

Kullandığınız operatöre bakar. [ ] ve [[ ]] içinde -eq, -lt, -gt sayısal, =, ==, != ise metin karşılaştırmasıdır. (( )) içindeki == ve < ise her zaman sayısaldır. Yanlış operatör seçmek, "10" ile "9" gibi değerlerde ters sonuç almanıza yol açabilir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Bash öğrenme rehberi · test ve [[ ]] farkı · case esac · && ve || operatörleri · aritmetik işlemler · exit kodları