Bash betiğinde hata ayıklamanın en hızlı yolu set -x (xtrace) seçeneğidir: açık olduğu sürece kabuk her komutu, değişkenler genişletildikten sonra ve çalıştırılmadan hemen önce, başına + koyarak standart hataya yazar. Betiğin tamamını izlemek için dosyayı bash -x betik.sh diye çalıştırabilir, yalnızca bir bölümü izlemek için o bölümü set -x ve set +x arasına alabilirsiniz. set -n komutları çalıştırmadan sözdizimini denetler, set -v satırları okundukları gibi yazdırır.
Bu yazıda bu üç seçeneğin yanında izleme satırını zenginleştiren PS4 değişkenini, izleme çıktısını ayrı bir dosyaya yönlendiren BASH_XTRACEFD değişkenini, LINENO, FUNCNAME, BASH_SOURCE, BASH_LINENO dizilerini ve çağrı yığınını yazdıran caller komutunu GNU Bash 5.2.37 ile çalıştırılmış örneklerle anlatıyoruz. Diğer konular için Bash öğrenme rehberi sayfasına bakabilirsiniz.
set -x ile komutları izlemek

Kılavuza göre -x seçeneği basit komutları, for, case, select ve aritmetik for komutlarını, argümanları veya kelime listeleriyle birlikte, genişletme yapıldıktan sonra yazdırır. Bu "genişletmeden sonra" ayrıntısı izlemenin asıl değeridir: ekranda kodu değil, değişkenlerin gerçek değerleriyle çalışan komutu görürsünüz. Kabuk her izleme satırının başına PS4 değişkeninin genişletilmiş değerini koyar; varsayılan değer + ve bir boşluktur.
set -x
ad="Dünya"
echo "Merhaba $ad"
x=$(echo iç)
set +x
echo "izleme kapandı"
# çıktı: + ad=Dünya
# çıktı: + echo 'Merhaba Dünya'
# çıktı: Merhaba Dünya
# çıktı: ++ echo iç
# çıktı: + x=iç
# çıktı: + set +x
# çıktı: izleme kapandıÇıktıda dikkat edilecek üç nokta var. Birincisi, echo satırı $ad yerine değeriyle ve kabuğun yeniden kullanılabilir tırnaklamasıyla gösterildi; boşluk içeren bir değerin tek argüman olarak mı yoksa bölünerek mi geçtiğini buradan anlarsınız. İkincisi, komut ikamesi içindeki komut ++ ile başladı: kılavuza göre PS4 değerinin ilk karakteri iç içe her düzey için tekrarlanır. Üçüncüsü, set +x komutunun kendisi de izlendi, çünkü seçenek o komut çalışana kadar açıktı.
İzleme çıktısı standart hataya yazılır, betiğin normal çıktısı ise standart çıktıya. Bu örnekte ikisi aynı terminalde karışık görünüyor; bash -x betik.sh 2>iz.txt ile izlemeyi bir dosyaya ayırabilirsiniz. Akışların yönlendirilmesi için Bash yönlendirme: stdout ve stderr yazısına bakın.
Betiği bash -x ile çalıştırmak
Kılavuz, set ile kullanılan tek harfli seçeneklerin Bash çağrılırken de verilebileceğini belirtir. Betiğe dokunmadan tamamını izlemek için bash -x betik.sh yazmanız yeterlidir; bu, betiğin ilk satırına set -x eklemekle aynı etkiyi yaratır. Seçenekler birleştirilebilir: bash -xv hem satırları okundukları gibi hem de genişletilmiş hâlleriyle gösterir.
bash -x ornek2.sh rapor.txt
# çıktı: + dosya=rapor.txt
# çıktı: + echo 'işlenecek: rapor.txt'
# çıktı: işlenecek: rapor.txtBu örnekteki ornek2.sh yalnızca iki satırdan oluşuyor: dosya="$1" ve echo "işlenecek: $dosya". İzleme, $1 değerinin rapor.txt olarak geldiğini doğrudan gösteriyor. Bir argümanın beklediğiniz gibi geçmediğinden şüphelendiğinizde ilk bakılacak yer burasıdır; argümanların kendisi için Bash script argümanları yazısına bakabilirsiniz.
PS4 ile satır numarası ve fonksiyon adı eklemek
Varsayılan + öneki, uzun betiklerde hangi satırın hangi dosyadan geldiğini söylemez. PS4, istem dizesi gibi genişletildiği için içine değişken koyabilirsiniz. En kullanışlı üçlü, o anki dosya adını veren BASH_SOURCE, satır numarasını veren LINENO ve o anda çalışan fonksiyonun adını veren FUNCNAME dizisidir. PS4 değerini mutlaka tek tırnakla atayın; çift tırnak kullanırsanız $LINENO atama anında sabitlenir ve her satırda aynı numara görünür.
PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]:-main}: '
topla() {
local t=$(( $1 + $2 ))
echo "$t"
}
set -x
sonuc=$(topla 2 3)
set +x
echo "sonuç: $sonuc"
# çıktı: ++ ornek.sh:7:main: topla 2 3
# çıktı: ++ ornek.sh:3:topla: local t=5
# çıktı: ++ ornek.sh:4:topla: echo 5
# çıktı: + ornek.sh:7:main: sonuc=5
# çıktı: + ornek.sh:8:main: set +x
# çıktı: sonuç: 5FUNCNAME yalnızca bir fonksiyon çalışırken var olduğu için üst düzeyde ${FUNCNAME[0]:-main} varsayılan değere düşer. Fonksiyonun komut ikamesi içinde çağrıldığı ++ önekinden, toplamanın beklenen değeri verdiği de local t=5 satırından okunur. PS4 ve diğer istem değişkenlerinin kaçış dizileri Bash PS1 prompt özelleştirme yazısında anlatılıyor.
BASH_XTRACEFD: izlemeyi ayrı bir dosyaya yazmak

İzleme satırları varsayılan olarak standart hataya gider ve betiğin kendi hata mesajlarıyla karışır. BASH_XTRACEFD değişkenine geçerli bir dosya tanımlayıcısı numarası atarsanız Bash izleme çıktısını o tanımlayıcıya yazar. Kılavuz birkaç kural koyar: değişken kaldırıldığında ya da yeni bir değer atandığında eski tanımlayıcı kapatılır; değişkeni kaldırmak veya boş yapmak izlemeyi yeniden standart hataya yönlendirir. Değişkeni 2 yapıp sonra kaldırmak standart hatanın kapanmasına yol açar, bu yüzden bundan kaçının.
exec 5> ./iz.log
BASH_XTRACEFD=5
set -x
echo "bu satır ekrana gider"
sayi=42
set +x
echo "iz.log içeriği:"
cat ./iz.log
# çıktı: bu satır ekrana gider
# çıktı: iz.log içeriği:
# çıktı: + echo 'bu satır ekrana gider'
# çıktı: + sayi=42
# çıktı: + set +xexec 5> ./iz.log satırı, betiğin geri kalanı için 5 numaralı tanımlayıcıyı dosyaya bağlar. Bu yöntemle uzun süren bir betiğin izlemesini dosyaya alıp ekrandaki çıktıyı temiz tutabilirsiniz. exec ile tanımlayıcı açmanın ayrıntısı Bash dosya tanımlayıcıları ve exec yazısında.
set -n ve set -v: çalıştırmadan denetlemek
-n (noexec) seçeneği komutları okur ama çalıştırmaz. Kılavuz bunu betikte sözdizimi hatası aramak için önerir ve seçeneğin etkileşimli kabuklarda yok sayıldığını belirtir. Pratikte bash -n betik.sh komutu, betiği hiç çalıştırmadan ayrıştırır; hata yoksa sessizce 0 döndürür, varsa hatanın satırını yazar.
#!/usr/bin/env bash
echo "başladı"
if [[ -f ./ayar.conf ]]; then
echo "ayar var"
fi fibash -n kontrol.sh
echo "durum: $?"
# çıktı: kontrol.sh: line 5: syntax error near unexpected token `fi'
# çıktı: kontrol.sh: line 5: `fi fi'
# çıktı: durum: 2Ekrana hiçbir zaman "başladı" yazılmadı; betik yalnızca ayrıştırıldı. -n yalnızca sözdizimini denetler: yanlış yazılmış bir komut adı ya da tanımsız bir değişken bu denetimde yakalanmaz, çünkü bunlar ancak çalışma anında ortaya çıkar. Bu tür mesajların anlamı için Bash sık hata mesajları ve çözümleri yazısına bakabilirsiniz.
-v (verbose) seçeneği ise kabuk girdisini okunduğu gibi, genişletme yapmadan standart hataya yazar. -x çalışan komutun değerlerle dolu hâlini gösterirken -v kaynak koddaki hâlini gösterir; ikisini birlikte kullandığınızda her satırın önce yazıldığı gibi, sonra genişletilmiş hâlini görürsünüz.
set -v
ad="Ece"
echo "Merhaba $ad"bash ayrinti.sh
# çıktı: ad="Ece"
# çıktı: echo "Merhaba $ad"
# çıktı: Merhaba EceYukarıdaki üç satırlık ayrinti.sh çalıştırıldığında, set -v satırından sonraki her satır önce olduğu gibi, ardından komutun kendi çıktısı yazıldı. Bu seçenek açıkken yorum satırları da okundukları için çıktıya düşer; uzun açıklamalar içeren betiklerde çıktı kalabalıklaşabilir.
LINENO, FUNCNAME, BASH_SOURCE ve BASH_LINENO
Bash, çağrı yığınını üç paralel dizide tutar. FUNCNAME o anda yığında bulunan fonksiyonların adlarını içerir; 0. eleman çalışan fonksiyondur, en sondaki eleman ise main. BASH_SOURCE her fonksiyonun tanımlandığı dosyayı, BASH_LINENO ise her fonksiyonun çağrıldığı satırı verir. Kılavuzdaki eşleme şöyledir: ${FUNCNAME[$i]}, ${BASH_SOURCE[$i+1]} dosyasının ${BASH_LINENO[$i]} satırından çağrılmıştır. LINENO ise o anda çalışan satırın numarasıdır. Bu bilgilerle, hatanın nerede değil de nereden çağrıldığını söyleyen bir yardımcı yazabilirsiniz.
hata() {
echo "HATA: $1 (${BASH_SOURCE[1]}:${BASH_LINENO[0]}, fonksiyon: ${FUNCNAME[1]})" >&2
}
yapilandir() {
[[ -r ./ayar.conf ]] || hata "ayar.conf okunamadı"
}
yapilandir
# çıktı: HATA: ayar.conf okunamadı (ornek.sh:5, fonksiyon: yapilandir)hata fonksiyonu, kendi konumu yerine (indeks 0) kendisini çağıran fonksiyonun bilgilerini (indeks 1) yazıyor; böylece mesaj, sorunun yaşandığı yapilandir fonksiyonunu ve beşinci satırı gösteriyor. Kılavuza göre bu dizilere atama yapmanın etkisi yoktur. source ile yüklenen kütüphane dosyalarında BASH_SOURCE farklı dosya adları içerir ve "betik doğrudan mı çalıştırıldı" kontrolü de bu diziyle yapılır; bu kullanım Bash source ve nokta komutu yazısında.
caller ile çağrı yığınını yazdırmak
caller yerleşik komutu, etkin bir alt yordamın (fonksiyon ya da source ile çalışan dosya) bağlamını yazar. Argümansız çağrıldığında satır numarasını ve dosya adını verir. Sıfır veya pozitif bir tamsayı verdiğinizde yığının o konumundaki satır numarasını, alt yordam adını ve dosyayı yazar; geçerli çerçeve 0'dır. Geçerli olmayan bir konum istendiğinde sıfır dışı değer döndürdüğü için, bir döngüyle tüm yığını yazdırmak kolaydır.
yigin() {
local i=0
while caller "$i"; do
i=$(( i + 1 ))
done
}
ic() { yigin; }
dis() { ic; }
dis
# çıktı: 7 ic ornek.sh
# çıktı: 8 dis ornek.sh
# çıktı: 9 main ornek.shHer satır "çağrının yapıldığı satır, çağıran alt yordam, dosya" düzenindedir: yigin 7. satırda ic içinden, ic 8. satırda dis içinden, dis ise 9. satırda ana betikten çağrılmış. Bu döngüyü bir ERR trap'ine bağlamak, hata anında tam bir yığın izi üretir. Trap kullanımı için Bash trap yazısına bakabilirsiniz.
DEBUG trap ile adım adım izleme
trap '...' DEBUG, kılavuza göre her basit komuttan ve for, case, select, (( )), [[ ]], aritmetik for komutlarından önce, ayrıca bir fonksiyonun ilk komutundan önce çalışır. BASH_COMMAND değişkeni o anda çalışmak üzere olan komutu, genişletilmemiş hâliyle verir. Bu, set -x çıktısından farklı bir bakış sunar: değerleri değil, kaynak koddaki komutu görürsünüz.
trap 'echo "[satır $LINENO] $BASH_COMMAND" >&2' DEBUG
toplam=0
for n in 1 2; do
toplam=$(( toplam + n ))
done
trap - DEBUG
echo "toplam: $toplam"
# çıktı: [satır 2] toplam=0
# çıktı: [satır 3] for n in 1 2
# çıktı: [satır 4] toplam=$(( toplam + n ))
# çıktı: [satır 3] for n in 1 2
# çıktı: [satır 4] toplam=$(( toplam + n ))
# çıktı: [satır 6] trap - DEBUG
# çıktı: toplam: 3shopt -s extdebug açıldığında DEBUG trap'i daha da güçlenir: kılavuza göre trap komutu sıfır dışı değer döndürürse sıradaki komut atlanır, 2 döndürürse ve bir alt yordamın içindeyseniz return çağrılmış gibi davranılır. Bu özellikler Bash için yazılmış hata ayıklayıcıların temelidir; günlük kullanımda set -x ve iyi bir PS4 çoğu zaman yeterlidir.
İzlemeyi seçici açmak
Her çalıştırmada izleme görmek istemezsiniz. İki yaygın yöntem vardır. Birincisi, izlemeyi bir ortam değişkenine bağlamaktır; betiği normal çalıştırdığınızda sessizdir, DEBUG=1 ile çalıştırdığınızda izleme açılır. İkincisi, izlemeyi yalnızca şüphelendiğiniz fonksiyonda açmaktır. Kılavuza göre fonksiyon içinde local - yazarsanız, o noktadan sonra set ile yapılan seçenek değişiklikleri fonksiyon bitince eski hâline döner; böylece set +x yazmayı unutsanız bile izleme fonksiyonun dışına taşmaz.
[[ ${DEBUG:-0} == 1 ]] && set -x
supheli() {
local -
set -x
local yol="./veri/$1"
echo "$yol"
}
supheli rapor.txt
echo "fonksiyondan sonra: $-"
# çıktı: + local yol=./veri/rapor.txt
# çıktı: + echo ./veri/rapor.txt
# çıktı: ./veri/rapor.txt
# çıktı: fonksiyondan sonra: hBÇıktının son satırındaki $- değerinde x harfi yok; yani izleme fonksiyon bitince kendiliğinden kapandı. Betiği DEBUG=1 bash ornek.sh diye çalıştırsaydınız ilk satır izlemeyi tüm betik için açacaktı. Yerel değişkenler ve local komutunun diğer kullanımları için Bash local değişken ve kapsam yazısına bakabilirsiniz.
Araçların karşılaştırması

| Araç | Ne gösterir | Ne zaman kullanılır |
|---|---|---|
| set -x veya bash -x | Genişletilmiş komutlar, PS4 önekiyle | Değerlerin ve argümanların doğruluğunu görmek |
| set -v | Satırların okunduğu hâli | Kaynak kodun hangi sırayla okunduğunu izlemek |
| set -n veya bash -n | Yalnızca sözdizimi hataları | Betiği çalıştırmadan denetlemek |
| PS4 | İzleme satırı öneki | Dosya, satır ve fonksiyon bilgisi eklemek |
| BASH_XTRACEFD | İzlemenin gideceği tanımlayıcı | İzlemeyi hata mesajlarından ayırmak |
| caller, FUNCNAME, BASH_LINENO | Çağrı yığını | Hata mesajına konum eklemek |
| trap DEBUG | Her komuttan önce BASH_COMMAND | Adım adım izleme |
Sık yapılan hatalar
- PS4 değerini çift tırnakla atamak.
$LINENOatama anında sabitlenir; tek tırnak kullanın. - İzleme çıktısını betiğin çıktısı sanmak. İzleme standart hataya gider; boru ile başka bir komuta aktarılmaz.
- bash -n geçti diye betiğin doğru olduğunu düşünmek. Bu denetim yalnızca sözdizimine bakar.
- Gizli bilgileri izlemeye düşürmek.
set -xaçıkken parola veya anahtar içeren değişkenler de açık metin olarak yazılır; bu bölümlerde izlemeyi kapatın. - set +x yazmayı unutmak. Fonksiyon içinde
local -kullanarak seçenekleri otomatik geri yükleyin. - BASH_XTRACEFD değişkenini 2 yapıp sonra kaldırmak. Kılavuza göre bu standart hatayı kapatır.
Sık Sorulan Sorular
set -x çıktısını sadece bir dosyaya nasıl yazarım?
İki yol var: betiği bash -x betik.sh 2>iz.txt diye çalıştırıp standart hatanın tamamını dosyaya almak ya da betik içinde exec 5>iz.txt ve BASH_XTRACEFD=5 ile yalnızca izlemeyi ayırmak. İkinci yöntem betiğin kendi hata mesajlarını ekranda bırakır.
set -x ile set -e birlikte kullanılabilir mi?
Evet ve hata ayıklarken çok yararlıdır: betik durduğunda izlemenin son satırı, sorunu yaratan komutu değerleriyle birlikte gösterir. set -e seçeneğinin hangi durumlarda devreye girmediğini Bash set -euo pipefail yazısında bulabilirsiniz.
set -x etkileşimli terminalde de çalışır mı?
Evet. set -x terminalde açıldığında yazdığınız her komutun genişletilmiş hâli çalışmadan önce gösterilir; bir takma adın veya bir değişkenin neye dönüştüğünü görmek için kullanışlıdır. set +x ile kapatırsınız. Buna karşılık kılavuz, -n seçeneğinin etkileşimli kabuklarda yok sayıldığını belirtir; sözdizimi denetimini her zaman bash -n dosya.sh biçiminde bir dosya üzerinde yapın.
İzlemedeki tırnaklar neden koddakinden farklı?
set -x komutu kaynak koddaki hâliyle değil, genişletilmiş argümanlarıyla gösterir ve argümanları yeniden girdi olarak kullanılabilecek biçimde tırnaklar. Bu yüzden echo "Merhaba $ad" satırı echo 'Merhaba Dünya' olarak görünür; tek tırnak, bu metnin tek bir argüman olduğunu gösterir.
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 · Dosya tanımlayıcıları ve exec · source ve nokta komutu · Bash hata mesajları