Bash'te her komut bittiğinde 0 ile 255 arasında bir çıkış durumu (exit status) bırakır ve bu değer hemen ardından $? özel parametresiyle okunabilir. Kural basittir: 0 başarı, 0 dışındaki her değer başarısızlık demektir. Bash kendi özel değerlerini de kullanır: komut bulunamazsa 127, bulunur ama çalıştırılamazsa 126, N numaralı ölümcül bir sinyalle sonlanırsa 128+N. Betiğin kendi çıkış kodunu exit n, bir fonksiyonun dönüş değerini return n belirler.
Bu yazıda $? değerinin ne zaman değiştiğini, kılavuzun tanımladığı özel kodları, 255'ten büyük değerlerin nasıl kırpıldığını, exit ile return farkını ve kendi betiklerinizde anlamlı çıkış kodlarını nasıl tasarlayacağınızı GNU Bash 5.2.37 ile çalıştırılmış örneklerle anlatıyoruz. Bash öğrenme yolunun tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz.
Neden 0 başarı demek?

İlk bakışta ters görünse de bu seçimin mantığı kılavuzda açıklanır: başarının tek ve net bir anlamı vardır, başarısızlığın ise pek çok türü olabilir. 0 değeri "her şey yolunda" demek için ayrılır, 1'den 255'e kadar olan değerler farklı hata türlerini ayırt etmek için kullanılır. Bash'in if, while, until komutları ve && / || listeleri karar verirken doğrudan bu durumu kullanır. true yerleşik komutu hiçbir şey yapmadan 0, false ise hiçbir şey yapmadan sıfır dışı bir değer döndürür.
$? her komuttan sonra değişir
$? yalnızca en son çalıştırılan komutun durumunu tutar. Bu yüzden durumu bir kez yazdırmak ya da test etmek bile değeri değiştirir. Aşağıdaki örnekte ikinci echo, false komutunun değil, ilk echo komutunun durumunu görür.
false
echo "ilk okuma: $?"
echo "ikinci okuma: $?"
# çıktı: ilk okuma: 1
# çıktı: ikinci okuma: 0
true
echo "true sonrası: $?"
# çıktı: true sonrası: 0Durumu birden fazla yerde kullanacaksanız hemen bir değişkene alın: komut; durum=$?. Kılavuz, Bash'in yerleşik komutlarının başarıda 0, başarısızlıkta sıfır dışı değer döndürdüğünü ve yanlış kullanım (geçersiz seçenek, eksik argüman) için genellikle 2 kullandığını da belirtir.
return
echo "return durumu: $?"
read -Q
echo "read durumu: $?"
# çıktı: ornek.sh: line 1: return: can only `return' from a function or sourced script
# çıktı: return durumu: 2
# çıktı: ornek.sh: line 3: read: -Q: invalid option
# çıktı: read: usage: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
# çıktı: read durumu: 2Bash'in özel çıkış kodları: 126, 127 ve 128+N
Kılavuzun 3.7.5 bölümü, kabuğun belirli hata türleri için kullandığı değerleri tanımlar. Komut adı bir fonksiyon, yerleşik komut ya da PATH içinde bir dosya olarak bulunamazsa, komutu çalıştırmak için oluşturulan alt süreç 127 döndürür. Komut bulunur ama çalıştırılamazsa sonuç 126 olur. Aşağıdaki örnekte bir klasörü komut gibi çalıştırmaya çalışıyoruz; yol doğru ama hedef çalıştırılabilir bir program değil.
mkdir -p ./klasor
./klasor
echo "klasör çalıştırma: $?"
olmayan_komut_xyz
echo "bulunamayan komut: $?"
# çıktı: ornek.sh: line 2: ./klasor: Is a directory
# çıktı: klasör çalıştırma: 126
# çıktı: ornek.sh: line 4: olmayan_komut_xyz: command not found
# çıktı: bulunamayan komut: 127126 kodunu en sık, çalıştırma izni olmayan bir betiği ./betik.sh diye çağırdığınızda görürsünüz; mesaj bu durumda Permission denied olur. İzinlerin nasıl verildiği Linux dosya izinleri ve chmod yazısında, bu ve benzer mesajların çözümleri Bash sık hata mesajları yazısında anlatılıyor.
Bir komut N numaralı ölümcül bir sinyalle sonlanırsa Bash çıkış kodu olarak 128+N kullanır. SIGTERM sinyalinin numarası 15 olduğu için kill ile sonlandırılan bir sürecin durumu 143 olur. kill -l, 128'den büyük bir koda karşılık gelen sinyalin adını yazar.
sleep 5 &
kill -TERM "$!"
wait "$!"
kod=$?
echo "sleep durumu: $kod"
echo "sinyal: $(kill -l "$kod")"
# çıktı: sleep durumu: 143
# çıktı: sinyal: TERMCtrl-C ile kesilen bir komut aynı mantıkla genellikle 130 (128+2, SIGINT) değerini bırakır. Sinyallerin kendisi ve betikte nasıl yakalandıkları Bash trap ile sinyal ve temizlik yazısının konusu.
Genişletme veya yönlendirme sırasında bir hata olursa da durum sıfırdan büyük olur. Örneğin var olmayan bir dosyadan girdi yönlendirmek, komut hiç çalışmadan hatayla sonuçlanır.
: < ./yok.txt
echo "yönlendirme hatası: $?"
# çıktı: ornek.sh: line 1: ./yok.txt: No such file or directory
# çıktı: yönlendirme hatası: 10-255 aralığı ve 8 bit kırpma

Çıkış durumu 0 ile 255 arasında tutulur. return için kılavuz bunu açıkça söyler: verilen sayının yalnızca en anlamsız 8 biti kullanılır. Pratikte bu, değerin 256'ya göre modunun alınması demektir; 256 sıfıra, 300 ise 44'e dönüşür. Negatif değerler de aynı kurala göre aralığın içine sarılır. exit ile denediğimizde de aynı sonuçları aldık. Bu yüzden 255'ten büyük kodlar kullanmayın; özellikle hesaplanan bir sayıyı (örneğin hata adedini) doğrudan çıkış kodu yapmak, 256 hatada "başarı" bildirmek anlamına gelebilir.
( exit 256 ); echo "exit 256 -> $?"
( exit 300 ); echo "exit 300 -> $?"
( exit -1 ); echo "exit -1 -> $?"
f() { return 300; }
f; echo "return 300 -> $?"
# çıktı: exit 256 -> 0
# çıktı: exit 300 -> 44
# çıktı: exit -1 -> 255
# çıktı: return 300 -> 44Örnekte exit komutlarını parantez içinde çalıştırdık; parantez bir alt kabuk açtığı için exit yalnızca o alt kabuğu kapatır ve betik devam eder. Bu farkın ayrıntısı Bash subshell ve çalışma ortamı yazısında.
exit ve return farkı
exit [n] kabuğu (betik içinde betiğin kendisini) sonlandırır ve n değerini üst sürece iletir. n verilmezse son çalıştırılan komutun durumu kullanılır ve kabuk kapanmadan önce varsa EXIT trap'i çalışır. return [n] ise yalnızca bir fonksiyonu veya source ile okunan bir dosyayı bitirir ve çağırana döner; n verilmezse yine son komutun durumu kullanılır. Fonksiyon dışında ve source edilmemiş bir betikte return kullanmak, yukarıda gördüğünüz gibi 2 durumuyla hata verir.
dosya_var_mi() {
[[ -e $1 ]]
}
durdur() {
echo "kritik hata: $1" >&2
exit 4
}
if dosya_var_mi ./yok.txt; then echo "var"; else echo "yok (dönüş: 1)"; fi
dosya_var_mi ./ornek.sh && echo "betik dosyası var"
durdur "ayar dosyası okunamadı"
echo "buraya gelinmez"
# çıktı: yok (dönüş: 1)
# çıktı: betik dosyası var
# çıktı: kritik hata: ayar dosyası okunamadı
# $? = 4dosya_var_mi fonksiyonunda return yazılmadı; fonksiyonun durumu, gövdedeki son komut olan [[ ]] testinin durumudur. Bu kalıp, "evet/hayır" cevabı veren fonksiyonlar için en temiz yoldur. Fonksiyondan metin döndürmek ise başka bir iştir: çıkış durumu yalnızca küçük bir sayıdır, metin veya büyük sayı için echo ile yazıp komut ikamesiyle yakalarsınız. Bu ayrım Bash fonksiyon tanımlama yazısında örneklerle anlatılıyor.
Çıkış durumunu doğru kontrol etmek
if komutu bir koşul ifadesi değil, bir komutun çıkış durumunu kontrol eder. Bu yüzden komut; if [ $? -eq 0 ]; then yazmak yerine komutu doğrudan if içine koymak hem daha kısa hem de daha güvenlidir: arada başka bir komut çalışıp $? değerini değiştiremez. Başarısızlık durumunu sınamak için ! kullanılır; kılavuza göre ! bir borunun çıkış durumunu mantıksal olarak tersine çevirir.
if grep -q "kök" <<< "kök dizin"; then
echo "eşleşme bulundu"
fi
if ! grep -q "yok" <<< "kök dizin"; then
echo "eşleşme yok"
fi
! true
echo "! true durumu: $?"
# çıktı: eşleşme bulundu
# çıktı: eşleşme yok
# çıktı: ! true durumu: 1Bir boruda yalnızca son komutun durumu $? değerine yansır. Her elemanın durumunu görmek için PIPESTATUS dizisi, başta kalan hataları yakalamak için set -o pipefail kullanılır; ikisi de Bash pipe, PIPESTATUS ve pipefail yazısında. Koşul yazımının kendisi için Bash if elif else yazısına bakabilirsiniz.
true | false | true
echo "son komut: $?"
true | false | true
echo "tüm boru: ${PIPESTATUS[*]}"
# çıktı: son komut: 0
# çıktı: tüm boru: 0 1 0Atamaların ve komut ikamesinin durumu
Yalnızca değişken atamasından oluşan bir satırın da bir çıkış durumu vardır. Kılavuzun 3.7.1 bölümüne göre böyle bir satırda komut adı kalmazsa ve genişletmeler arasında bir komut ikamesi varsa, satırın durumu son yapılan komut ikamesinin durumudur; hiç komut ikamesi yoksa durum 0'dır. Bu kural, bir komutun çıktısını alırken başarısını da aynı satırda sınamanızı sağlar. Ancak atama local, export veya declare gibi bir yerleşik komutun argümanı olduğunda durum artık o yerleşik komuta aittir ve içerideki hata kaybolur.
sonuc=$(false)
echo "yalın atama: $?"
sonuc=$(true)
echo "başarılı ikame: $?"
export SONUC=$(false)
echo "export ile: $?"
if surum=$(bash --version); then
echo "komut çalıştı, çıktı alındı"
fi
# çıktı: yalın atama: 1
# çıktı: başarılı ikame: 0
# çıktı: export ile: 0
# çıktı: komut çalıştı, çıktı alındıSon örnekteki if degisken=$(komut) kalıbı, çıktıyı değişkene alırken komut başarısızsa farklı bir yola sapmanın kısa yoludur. export satırında görülen kayıp ise set -e kullanan betiklerde sık rastlanan bir tuzaktır; ayrıntısı set -euo pipefail yazısındaki errexit tuzakları bölümünde. Komut ikamesinin kendisi için Bash komut ikamesi yazısına bakabilirsiniz.
Arka plan işlerinin durumu ve wait
Bir komutu & ile arka plana attığınızda kabuk onun bitmesini beklemez; kılavuza göre bu durumda dönüş değeri hemen 0 olur. Yani $? o anda işin sonucunu değil, yalnızca "iş başlatıldı" bilgisini verir. Gerçek sonucu almak için wait yerleşik komutunu kullanırsınız: wait, verilen sürecin bitmesini bekler ve onun çıkış durumunu döndürür. Verilen kimlik kabuğun çocuğu değilse sonuç 127 olur.
( sleep 0.2; exit 7 ) &
echo "& sonrası: $?"
wait "$!"
echo "wait ile alınan: $?"
# çıktı: & sonrası: 0
# çıktı: wait ile alınan: 7Birden fazla işi paralel başlatıp her birinin sonucunu toplamak istediğinizde, her işin süreç numarasını $! ile bir diziye kaydedip ardından tek tek wait çağırmak yaygın bir kalıptır. İş denetimi, jobs ve %1 gibi iş numaraları Bash arka plan işleri yazısında anlatılıyor.
Kendi betiğiniz için çıkış kodu tasarlamak
Betiğiniz başka bir betikten, bir zamanlanmış görevden veya bir servis yöneticisinden çağrılacaksa, anlamlı çıkış kodları çağıranın hatayı ayırt etmesini sağlar. Kılavuzdaki genel eğilimi izlemek iyi bir başlangıçtır: 0 başarı, 1 genel hata, 2 yanlış kullanım. 126 ve üstü kabuğun kendi anlamları için kullanıldığından, kendi kodlarınızı 125'in altında tutmak çakışmayı önler. Kodları betiğin başında salt okunur sabitler olarak tanımlamak okunabilirliği artırır.
#!/usr/bin/env bash
readonly BASARI=0 KULLANIM_HATASI=2 GIRDI_YOK=3
kullanim() {
echo "kullanım: $0 DOSYA" >&2
exit "$KULLANIM_HATASI"
}
[[ $# -eq 1 ]] || kullanim
[[ -r $1 ]] || { echo "okunamıyor: $1" >&2; exit "$GIRDI_YOK"; }
echo "işleniyor: $1"
exit "$BASARI"
# çıktı: kullanım: rapor.sh DOSYA
# $? = 2Betik argümansız çağrıldığı için kullanım mesajı yazıldı ve 2 ile çıkıldı. Aynı betik okunamayan bir dosyayla çağrılsaydı 3, geçerli bir dosyayla çağrılsaydı 0 dönecekti. Argümanların sayısı ve $0 gibi özel parametreler Bash script argümanları yazısında, seçenek ayrıştırma ve kullanım mesajı kalıbı ise Bash getopts yazısında anlatılıyor.
Çıkış kodları özet tablosu
| Değer | Anlamı | Kaynak |
|---|---|---|
| 0 | Başarı | Tüm komutlar |
| 1 | Genel hata | Çoğu komut ve yerleşik; genişletme veya yönlendirme hatası |
| 2 | Yerleşik komutun yanlış kullanımı | Geçersiz seçenek, eksik argüman |
| 126 | Komut bulundu ama çalıştırılamadı | İzin yok, hedef bir klasör |
| 127 | Komut bulunamadı | Yanlış yazılmış ad, PATH dışında |
| 128+N | N numaralı sinyalle sonlandı | 130 = INT (2), 143 = TERM (15) |
Sık yapılan hatalar

- $? değerini iki kez okumak. İlk
echoveya[ ]testinden sonra değer değişir; hemen bir değişkene kaydedin. - 255'ten büyük kod kullanmak. Değer 8 bite kırpılır; 256 sıfıra dönüşür.
- Fonksiyondan metin döndürmek için return kullanmak.
returnyalnızca durum kodu taşır; metniechoile yazıp$( )ile alın. - Fonksiyon içinde exit ile return'ü karıştırmak.
exittüm betiği sonlandırır; yalnızca fonksiyondan çıkmak içinreturnkullanın. - Hatada da exit 0 ile çıkmak. Çağıran araçlar hatayı göremez.
- Komutu çalıştırıp sonra if [ $? -eq 0 ] yazmak. Komutu doğrudan
ifiçine koymak daha güvenlidir.
Sık Sorulan Sorular
exit komutunu sayı vermeden yazarsam ne olur?
Kılavuza göre n verilmezse çıkış durumu, son çalıştırılan komutun durumudur. Örneğin son komut başarısız olduysa betik de başarısız olarak biter. Niyetinizi açık göstermek için betiğin sonunda exit 0 veya anlamlı bir sabit yazmanız önerilir.
Betiğin tamamı hangi çıkış koduyla biter?
exit çağrılmadıysa Bash son çalıştırılan komutun durumuyla çıkar. Bir sözdizimi hatası olursa kılavuza göre sıfır dışı bir değerle sonlanır. set -e açıksa başarısız olan komutun durumu betiğin çıkış kodu olur; bu seçenek için Bash set -euo pipefail yazısına bakın.
Sinyalle ölen bir komutu normal hatadan nasıl ayırırım?
Durum 128'den büyükse komutun bir sinyalle sonlanmış olması muhtemeldir; kill -l "$durum" sinyalin adını verir. Ancak bir program kendi isteğiyle de 128'den büyük bir kodla çıkabilir, bu yüzden bu yorum her zaman kesin değildir.
Bir betiğin çıkış kodunu başka bir betikten nasıl okurum?
Çağırdığınız betik de bir komuttur; onu bash ./alt.sh veya ./alt.sh diye çalıştırdıktan hemen sonra $? değeri, alt betiğin exit ile bildirdiği kodu verir. Kodu doğrudan karar için kullanmak daha temizdir: if ./alt.sh; then ...; fi veya farklı kodlara göre dallanmak için ./alt.sh; durum=$? ardından case $durum in 2) ... ;; 3) ... ;; esac. Alt betik kendi içinde hangi kodları kullandığını belgelediğinde bu dallanma güvenilir hâle gelir.
Çıkış kodunu bir değişkende saklamak gerekir mi?
Değeri bir kez ve hemen kullanacaksanız gerekmez. Birden fazla karşılaştırma yapacak, kayıt yazacak veya temizlik sonrası aynı kodla çıkacaksanız durum=$? ile saklamak, aradaki komutların değeri ezmesini önler.
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 · set -euo pipefail · Bash trap · Bash fonksiyonlar · PIPESTATUS · Bash hata mesajları