Bash'te ;, &, && ve || operatörleri komutları birbirine bağlayarak liste oluşturur. ; komutları sırayla, birinin bitmesini bekleyerek çalıştırır; & soldaki komutu arka planda başlatıp beklemeden devam eder; komut1 && komut2 yalnızca ilk komut başarılı olursa (çıkış durumu 0) ikinciyi çalıştırır; komut1 || komut2 ise yalnızca ilk komut başarısız olursa ikinciyi çalıştırır. && ve || aynı önceliğe sahiptir ve soldan sağa değerlendirilir.

Bu yazıda kılavuzun 3.2.4 bölümündeki liste kurallarını, öncelik ve gruplama ilişkisini, komut || exit 1 gibi kısa hata kalıplarını, sık kullanılan ama tam bir if-else olmayan a && b || c tuzağını ve bu operatörlerin set -e ile nasıl etkileştiğini göreceksiniz. Örnekler GNU Bash 5.2.37 ile çalıştırıldı.

Liste nedir?

Liste nedir?

Kılavuzun tanımına göre bir liste, ;, &, && ya da || operatörlerinden biriyle ayrılmış bir ya da daha fazla boru hattından oluşur ve isteğe bağlı olarak ;, & veya yeni satırla bitebilir. Boru hattı ise | ile bağlanmış komutlardır; tek bir komut da tek elemanlı bir boru hattıdır. Yani a | b && c ifadesinde önce a | b boru hattı oluşur, && bu hattın sonucuna bakar. Boru hatları pipe, PIPESTATUS ve pipefail yazısında ayrıca anlatılıyor.

Listede bir ya da daha fazla yeni satır, noktalı virgülle aynı işi görür. Betiğe alt alta yazdığınız komutlar da aslında yeni satırlarla ayrılmış bir listedir. Bu yüzden betikteki her satırın bir öncekinin bitmesini beklemesi, ; operatörünün davranışından gelir.

Sıralı çalıştırma: ; ve yeni satır

; ile ayrılan komutlar sırayla çalışır ve kabuk her birinin bitmesini bekler. Kılavuza göre listenin dönüş durumu, çalışan son komutun durumudur; önceki komutların başarılı ya da başarısız olması sonucu etkilemez. Bu, ; operatörünün hata durumlarını görmezden geldiği anlamına gelir: önceki adım başarısız olsa bile sonraki adım çalışır.

bash
#!/usr/bin/env bash
cd ./olmayan 2>/dev/null; echo "yine de çalıştı"
# çıktı: yine de çalıştı

false; true
# $? = 0
true; false
# $? = 1

İlk satır neden dikkatli olmanız gerektiğini gösteriyor: cd başarısız olduğu halde sonraki komut çalıştı. Eğer sonraki komut rm gibi bir şey olsaydı, beklediğiniz dizinde değil o anki dizinde çalışırdı. Bir adımın başarısına bağlı adımları ; ile değil && ile bağlayın.

Koşullu çalıştırma: && ve ||

Kılavuz bu iki operatörün oluşturduğu yapılara AND listesi ve OR listesi der. komut1 && komut2 biçiminde ikinci komut yalnızca ve yalnızca birinci komut sıfır durumla bittiğinde çalışır. komut1 || komut2 biçiminde ikinci komut yalnızca birinci komut sıfırdan farklı bir durumla bittiğinde çalışır. İki yapının dönüş durumu da listede çalışan son komutun durumudur. Çalışmayan komutlar sonucu etkilemez; bu, kısa devre değerlendirme olarak bilinir.

bash
#!/usr/bin/env bash
mkdir -p ./proje

cd ./proje && echo "proje dizinine girildi"
# çıktı: proje dizinine girildi
cd ./olmayan 2>/dev/null || echo "olmayan dizine girilemedi"
# çıktı: olmayan dizine girilemedi

false && echo "çalışmaz"
# $? = 1
true || echo "çalışmaz"
# $? = 0

Son iki satırdaki durumlara dikkat edin. false && echo listesinde echo hiç çalışmadı, bu yüzden listenin durumu false komutundan gelen 1 oldu. true || echo listesinde de echo çalışmadı ve durum true komutundan gelen 0 oldu. Komutların çıkış durumlarının anlamı Bash exit kodları yazısında ayrıntılı olarak anlatılıyor.

Öncelik ve soldan sağa değerlendirme

Öncelik ve soldan sağa değerlendirme

Kılavuza göre && ve || eşit önceliğe sahiptir, ; ve & ise bunlardan daha düşük ve kendi aralarında eşit önceliğe sahiptir. AND ve OR listeleri soldan sağa (sol birleşmeli) değerlendirilir. Bu kural, matematikteki "ve işlemi veya işleminden önce gelir" alışkanlığıyla çelişir; birçok programlama dilinde && önceliklidir, Bash'in komut listelerinde değildir.

bash
#!/usr/bin/env bash
true || echo "A" && echo "B"
# çıktı: B

false && echo "A" || echo "C"
# çıktı: C

echo "1"; false && echo "2"; echo "3"
# çıktı:
#   1
#   3

İlk satır soldan sağa şöyle okunur: (true || echo A) && echo B. Parantez içi true sayesinde 0 döner, bu yüzden echo B çalışır. Eğer && öncelikli olsaydı ifade true || (echo A && echo B) olarak okunur ve hiçbir şey yazılmazdı. Üçüncü satırda ise ; en düşük öncelikte olduğu için satır üç ayrı listeye bölünür; ortadaki liste başarısız olsa da üçüncü echo çalışır.

Uzun bir listeyi okunaklı hale getirmek için satırı && ya da || operatörüyle bitirip alt satırdan devam edebilirsiniz; Bash, operatörden sonra komutun bitmediğini bilir ve bir sonraki satırı okur. Bu, ters bölü ile satır birleştirmeye göre daha güvenlidir, çünkü ters bölüden sonra yanlışlıkla kalan bir boşluk birleştirmeyi bozar.

bash
#!/usr/bin/env bash
mkdir -p ./derleme &&
  cd ./derleme &&
  echo "derleme dizini hazır"
# çıktı: derleme dizini hazır

Gruplama: { } ve ( )

Önceliği değiştirmek ya da birden fazla komutu tek bir birim olarak bağlamak için gruplama kullanılır. Kılavuz iki yol tanımlar. { liste; } listeyi geçerli kabukta çalıştırır; süslü parantezler ayrılmış kelime olduğu için içerikten boşlukla ayrılmalı ve listeden sonra noktalı virgül ya da yeni satır gelmelidir. ( liste ) ise listeyi bir alt kabukta çalıştırır; içerideki değişken atamaları ve cd gibi değişiklikler dışarıya yansımaz. İki yapının dönüş durumu da listenin dönüş durumudur.

bash
#!/usr/bin/env bash
cd ./olmayan 2>/dev/null || { echo "hata: dizin yok"; echo "temizlik yapıldı"; }
# çıktı:
#   hata: dizin yok
#   temizlik yapıldı

sayi=1
( sayi=2; echo "alt kabukta: $sayi" )
echo "dışarıda: $sayi"
# çıktı:
#   alt kabukta: 2
#   dışarıda: 1

Süslü parantez olmadan komut || echo hata; exit 1 yazsaydınız ; düşük öncelikli olduğu için exit 1 listenin dışında kalır ve komut başarılı olsa bile çalışırdı. Hata durumunda birden fazla iş yapmak istiyorsanız her zaman || { ...; } biçimini kullanın. Alt kabuğun yalıtım özellikleri subshell ve çalışma ortamı yazısında anlatılıyor.

Hata kalıpları: komut || exit ve komut || return

Betiklerde en sık görülen liste kalıbı, başarısız olan bir adımda betiği durdurmaktır. cd "$hedef" || exit 1 satırı, dizine girilemezse betiği 1 koduyla sonlandırır ve sonraki komutların yanlış dizinde çalışmasını engeller. Fonksiyon içinde aynı iş için || return kullanılır. Hata mesajını standart hataya yazmak için >&2 yönlendirmesi eklenir. Kalıbı tekrar tekrar yazmamak için küçük bir yardımcı fonksiyon tanımlamak yaygın bir yöntemdir:

bash
#!/usr/bin/env bash
olum() {
  echo "hata: $*" >&2
  exit 1
}

hazirla() {
  cd "$1" 2>/dev/null || olum "$1 dizinine girilemedi"
  echo "$1 hazır"
}

mkdir -p ./site
( hazirla ./site )
# çıktı: ./site hazır
( hazirla ./yok )
# çıktı: hata: ./yok dizinine girilemedi
# $? = 1
echo "ana betik devam ediyor"
# çıktı: ana betik devam ediyor

Örneği tek dosyada gösterebilmek için her çağrıyı bir alt kabuğa aldık; exit yalnızca o alt kabuğu sonlandırdı ve ana betik devam etti. Gerçek bir betikte olum doğrudan bütün betiği sonlandırır. Hata yönetimini daha sistematik yapmak için set -euo pipefail ve trap ile temizlik yazılarına bakabilirsiniz.

a && b || c tam bir if-else değildir

Kısa koşullar için test && evet || hayir yazımı çok kullanılır ve çoğu zaman beklendiği gibi çalışır. Ancak soldan sağa değerlendirme kuralı nedeniyle anlamı "test doğruysa evet, değilse hayır" değildir; gerçek anlamı "(test ve evet) başarısızsa hayır" olur. Yani test doğru olduğu halde evet komutu başarısız olursa hayir komutu da çalışır:

bash
#!/usr/bin/env bash
kaydet() {
  echo "kaydediliyor"
  return 1   # kayıt başarısız oldu
}

[[ -n "veri" ]] && kaydet || echo "veri yok"
# çıktı:
#   kaydediliyor
#   veri yok

if [[ -n "veri" ]]; then
  kaydet
else
  echo "veri yok"
fi
# çıktı: kaydediliyor

İlk yazım hem kaydetmeye çalıştı hem de "veri yok" dedi; bu mantıksal olarak yanlış bir çıktıdır. if sürümü ise yalnızca bir dalı çalıştırdı. Orta komutun asla başarısız olmayacağından emin değilseniz (örneğin yalnızca bir değişken ataması değilse) if elif else kullanın. Ortadaki komut bir fonksiyon ya da dış bir programsa başarısız olma ihtimalini her zaman hesaba katın.

set -e ile && ve || listeleri

set -e (errexit) açık olan bir betikte başarısız olan komut betiği durdurur, ama listelerde önemli istisnalar vardır. Kılavuza göre kabuk, başarısız olan komut bir && ya da || listesinin parçasıysa, son && ya da || operatöründen sonraki komut hariç, çıkmaz. Bu nedenle false && true betiği durdurmaz. Daha az bilinen ikinci kural şudur: bir fonksiyon, -e ayarının yok sayıldığı bir bağlamda çalışırsa (örneğin fonksiyon || ... biçiminde çağrılırsa) gövdesindeki komutlar da -e ayarından etkilenmez:

bash
#!/usr/bin/env bash
set -e

false && true
echo "false && true betiği durdurmadı"

adim() {
  false
  echo "fonksiyon false satırından sonra devam etti"
}
adim || echo "bu satıra gelinmedi"
# çıktı:
#   false && true betiği durdurmadı
#   fonksiyon false satırından sonra devam etti

adim fonksiyonu || ile çağrıldığı için içindeki false fonksiyonu durdurmadı; fonksiyonun son komutu olan echo başarılı olduğundan || sonrası da çalışmadı. Hata yakalamanın tamamen set -e ayarına bırakıldığı betiklerde bu davranış hataların sessizce geçmesine yol açabilir. Bu istisnaların tam listesi set -euo pipefail yazısında ele alınıyor.

Arka planda çalıştırma: &

Bir komutun sonuna & koyduğunuzda kabuk onu bir alt kabukta, eşzamansız olarak başlatır ve bitmesini beklemeden bir sonraki komuta geçer. Kılavuz bunu arka planda çalıştırma olarak adlandırır ve böyle bir komutun dönüş durumunun her zaman 0 olduğunu belirtir; komutun gerçekten başarılı olup olmadığını öğrenmek için wait ile beklemeniz gerekir. En son başlatılan arka plan işinin süreç numarası $! özel parametresinde tutulur. İş denetimi etkin değilken, açık bir yönlendirme yoksa arka plan komutlarının standart girdisi /dev/null olur.

bash
#!/usr/bin/env bash
false &
# $? = 0
wait $!
# $? = 1

[[ $! == +([0-9]) ]] && echo "\$! bir süreç numarası tutuyor"
# çıktı: $! bir süreç numarası tutuyor

Örnekte false & satırı 0 döndürdü; gerçek sonuç olan 1, ancak wait $! ile alınabildi. Süreç numarası her çalıştırmada farklı olduğundan örnek yalnızca bir sayı olup olmadığını kontrol ediyor. & ile && karıştırılmamalıdır: komut1 & komut2 iki komutu aynı anda çalıştırır, komut1 && komut2 ise ikinciyi birincinin başarısına bağlar. Arka plan işlerinin yönetimi (jobs, fg, bg) için arka plan işleri yazısına bakın.

[[ ]] içindeki && ile komut listesindeki && farkı

&& ve || işaretleri [[ ]] koşul ifadesinin içinde de kullanılır, ama orada başka bir anlam taşırlar: komutları değil, koşul ifadelerini birleştirirler. [[ -f $a && -r $a ]] tek bir komuttur. [[ -f $a ]] && [[ -r $a ]] ise iki komutlu bir AND listesidir. Sonuç aynıdır, ama ikincisi [ ] ile de çalışır. (( )) içindeki && ise aritmetik mantıksal "ve" operatörüdür. Koşul ifadelerinin ayrıntıları test ve [[ ]] farkı yazısında.

Operatör özeti

OperatörSağdaki komut ne zaman çalışırListenin dönüş durumu
;Her zaman, soldaki bittikten sonraSon çalışan komutun durumu
yeni satır; ile aynıSon çalışan komutun durumu
&Hemen, soldaki beklenmedenArka plan komutu için 0
&&Soldaki 0 ile bittiyseSon çalışan komutun durumu
||Soldaki 0 dışında bir değerle bittiyseSon çalışan komutun durumu

Sık yapılan hatalar

Sık yapılan hatalar: Grup kullanmadan çoklu hata işlemi, Süslü parantezde boşluk ve noktalı virgül unutmak, a && b || c yapısını if-else sanmak, Bağımlı adımları ; ile bağlamak
  • Grup kullanmadan çoklu hata işlemi: komut || echo hata; exit 1 yazımında exit 1 her durumda çalışır. Doğrusu komut || { echo hata; exit 1; }.
  • Süslü parantezde boşluk ve noktalı virgül unutmak: {echo a} bir grup değil, {echo adlı bir komuttur. { echo a; } yazın.
  • a && b || c yapısını if-else sanmak: b başarısız olursa c de çalışır.
  • Bağımlı adımları ; ile bağlamak: cd dizin; rm -f ./* yazımında cd başarısız olursa silme işlemi yanlış dizinde yapılır. cd dizin && rm -f ./* ya da cd dizin || exit kullanın.
  • & sonrası durumu sonuç sanmak: Arka plan komutunun dönüş durumu her zaman 0'dır; gerçek sonuç için wait kullanın.

Sık Sorulan Sorular

Bash && ile ; arasındaki fark nedir?

; sağdaki komutu soldakinin sonucuna bakmadan çalıştırır. && ise sağdaki komutu yalnızca soldaki başarılı olduğunda çalıştırır. Bir adım bir öncekine bağlıysa && kullanın.

&& ve || hangisi önce değerlendirilir?

İkisi de eşit önceliktedir ve soldan sağa değerlendirilir. a || b && c ifadesi (a || b) && c olarak okunur. Farklı bir gruplama istiyorsanız { } kullanın.

Bir komut başarısız olursa betiği nasıl durdururum?

Tek bir komut için komut || exit 1 yazabilirsiniz. Betiğin tamamında genel bir kural istiyorsanız set -e kullanılabilir, ama bu yazıda gördüğünüz istisnalar nedeniyle kritik adımlarda açık kontrol yazmak daha güvenilirdir.

Bir komutun başarısız olmasını nasıl tersine çeviririm?

Komutun önüne ! koyun: ! komut. Kılavuza göre boru hattının başındaki ! çıkış durumunu mantıksal olarak tersler. Böylece ! cd ./dizin 2>/dev/null && echo yok gibi ifadeler yazabilirsiniz.

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 · if elif else · exit kodları · set -euo pipefail · pipe ve pipefail · arka plan işleri