İçeriğe geç
academia.sh

Ders 22 / 24

Betik Hata Ayıklama

Sözdizim denetimi, izleme kipi ve istem biçimi, izlemenin yönlendirilmesi, değişken ve çağrı yığını incelemesi.

İçindekiler

Betik düzgün duruyor ve arkasında iz bırakmıyor. Yanlış sonuç ürettiğinde bunu nasıl bulacağımız ayrı bir sorudur.

Kabuk betiklerinde hata ayıklamayı zorlaştıran iki özellik vardır. Birincisi, çalışan şeyin çoğunlukla kabuğun kendisi değil çağırdığı programlar olması; ikincisi, genişletme kurallarının sonucu yazılan metinden farklı kılabilmesi. İkinci nokta belirleyicidir: rm "$dizin/$ad" satırının ne sildiğini, kabuğun o satırı nasıl genişlettiğini görmeden bilemezsiniz.

Bu dersin araçları tam olarak o genişletilmiş hâli görünür kılar.

Çalıştırmadan Denetlemek

bash -n betiği ayrıştırır ama hiçbir komut çalıştırmaz. Betik Anatomisi dersinde tanıtılmıştı; yıkıcı işlem içeren betiklerde ilk adımdır.

Sınanan bozuk.sh, kapatılmamış bir koşul içerir:

#!/usr/bin/env bash
# kutuk ozeti — sozdizimi hatasi barindirir
kutuk="$1"

if [ -f "$kutuk" ]; then
  echo "satir sayisi: $(wc -l < "$kutuk")"
bash -n bozuk.sh
bozuk.sh: line 7: syntax error: unexpected end of file from `if' command on line 5

Hata iletisi iki satır numarası verir: sorunun fark edildiği yer ve açık kalan yapının başladığı yer. İkincisi genellikle daha yararlıdır.

-n yalnızca sözdizimini görür. Var olmayan komutlar, yanlış seçenekler, tanımsız değişkenler ve mantık hataları bu denetimden geçer. Bir betiğin bash -n ile temiz çıkması, çalışacağı anlamına gelmez.

İzleme Kipi

set -x seçeneği, her komutu genişletmelerden sonra ve çalıştırmadan önce standart hataya yazar. Betiğin tamamı için bash -x betik biçiminde de açılabilir.

bash -x ayikla.sh
+ kutuk=erisim.log
+ adet=3
+ en_cok erisim.log 3
+ cut '-d ' -f7 erisim.log
+ sort
+ uniq -c
+ sort -k1,1nr
+ head -3

Üç bilgi görülüyor. Değişkenler beklenen değerleri aldı. Fonksiyon erisim.log ve 3 argümanlarıyla çağrıldı. Boru hattının halkaları ayrı ayrı listelendi ve head -3 satırındaki 3, değişkenin genişlemiş hâli.

Dördüncü satırdaki '-d ' yazımına dikkat edin: kabuk, tırnaklama gerektiren argümanları izleme çıktısında tırnak içinde gösterir. Bu, boşluk içeren bir argümanın gerçekten tek parça geçip geçmediğini gösteren en doğrudan kanıttır.

İzleme İstemini Biçimlendirmek

Öntanımlı + öneki, uzun betiklerde hangi satırın çalıştığını söylemez. PS4 değişkeni bu öneki belirler ve her satır yazılmadan önce genişletilir.

export PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-ana}: '
bash -x ayikla.sh
+ ayikla.sh:2:ana: kutuk=erisim.log
+ ayikla.sh:3:ana: adet=3
+ ayikla.sh:7:ana: en_cok erisim.log 3
+ ayikla.sh:5:en_cok: cut '-d ' -f7 erisim.log
+ ayikla.sh:5:en_cok: sort
+ ayikla.sh:5:en_cok: uniq -c
+ ayikla.sh:5:en_cok: sort -k1,1nr
+ ayikla.sh:5:en_cok: head -3

Her satır artık dosya adı, satır numarası ve fonksiyon adı taşıyor. PS4 ataması tek tırnak içinde yazılmıştır: değişkenler atama anında değil, her izleme satırı yazılırken genişlemelidir.

FUNCNAME[0] üst düzeyde tanımsızdır; :-ana öntanımlı değeri set -u altında da çalışmasını sağlar.

Seçmeli İzleme

Uzun bir betiğin tamamını izlemek, aranan satırı binlerce satırlık çıktının içinde bırakır. set -x ve set +x çiftiyle yalnızca ilgilenilen bölüm izlenir.

#!/usr/bin/env bash
echo "izlenmeyen bolum"
set -x
kod=404
sinif="${kod:0:1}"
set +x
echo "sonuc: ${sinif}xx"
izlenmeyen bolum
+ kod=404
+ sinif=4
+ set +x
sonuc: 4xx

Kapanış satırı da izleme çıktısına düşer; kapanışın kendisi çalıştırılan bir komuttur.

İzlemeyi Ayırmak

İzleme çıktısı standart hataya gider. Betiğin uyarıları da oraya gittiği için, ikisi karışır ve betiğin çıktısını kütüğe alan bir zamanlanmış görevde okunmaz hâle gelir.

BASH_XTRACEFD değişkeni izlemeyi başka bir tanıtıcıya yönlendirir:

#!/usr/bin/env bash
exec 9> izleme.log
BASH_XTRACEFD=9
set -x
kod=404
sinif="${kod:0:1}"
set +x
echo "rapor ciktisi: ${sinif}xx"
rapor ciktisi: 4xx

izleme.log içeriği:

+ kod=404
+ sinif=4
+ set +x

Terminale yalnızca raporun kendisi düştü. Yönlendirme dersinde tanıtılan exec ile tanıtıcı açma deseni burada işlevini buluyor: 3 ve üzeri numaralar bu tür kullanımlar için serbesttir.

Ayrıntılı Kip

set -v, komutları genişletmeden önce, kaynak dosyadaki hâliyle yazar.

#!/usr/bin/env bash
set -v
kod=404
echo "sinif: ${kod:0:1}xx"
kod=404
echo "sinif: ${kod:0:1}xx"
sinif: 4xx

-x genişletilmiş hâli, -v yazılmış hâli gösterir. İkisi birlikte açıldığında bir genişletmenin girdisi ve çıktısı yan yana görülür; genişletme kurallarının yanlış anlaşıldığı durumlarda en doğrudan tanı yoludur.

Her Komuttan Önce Çalışan Kanca

trap ... DEBUG her basit komuttan önce çalışır. BASH_COMMAND değişkeni, çalıştırılmak üzere olan komutun metnini tutar.

#!/usr/bin/env bash
trap 'printf "[%s] %s\n" "$LINENO" "$BASH_COMMAND" >&2' DEBUG
kod=404
sinif="${kod:0:1}"
echo "sonuc: ${sinif}xx"
[3] kod=404
[4] sinif="${kod:0:1}"
[5] echo "sonuc: ${sinif}xx"
sonuc: 4xx

DEBUG kancası, set -x ile elde edilemeyen bir şey sağlar: kancanın gövdesi rasgele kod olabilir. Belirli bir değişken beklenmedik bir değere geçtiğinde durmak ya da her komuttan önce bir değişkenin değerini yazmak bu yolla yapılır.

Bedeli maliyettir: kanca her komut için çalışır. Üretim betiklerinde bırakılmaz.

Değişkenleri İncelemek

declare -p, bir değişkenin tipini ve içeriğini yeniden kullanılabilir biçimde yazar. Diziler için echo yeterli değildir; declare -p eleman sınırlarını gösterir.

kodlar=(200 404 500)
declare -A sayim=([404]=4 [500]=3)
declare -p kodlar sayim
declare -a kodlar=([0]="200" [1]="404" [2]="500")
declare -A sayim=([404]="4" [500]="3" )

-a dizinli, -A ilişkisel diziyi gösteriyor. Seyrek bir dizide dizinlerin atladığı, boş bir elemanın gerçekten boş olduğu ancak bu çıktıda görülür.

Çağrı Yığınını Okumak

Bash, çağrı zincirini üç koşut dizide tutar: FUNCNAME fonksiyon adlarını, BASH_SOURCE dosya adlarını, BASH_LINENO çağrı satırlarını. Bir hata işleyicisinde bunları yazmak, hatanın nereden geldiğini gösterir.

nerede() {
  local i
  for (( i = 1; i < ${#FUNCNAME[@]}; i++ )); do
    printf '  %s() <- %s:%s\n' "${FUNCNAME[i]}" "${BASH_SOURCE[i]}" "${BASH_LINENO[i-1]}"
  done
}
ic()  { nerede; }
dis() { ic; }
dis
  ic() <- ./yigin.sh:8
  dis() <- ./yigin.sh:9
  main() <- ./yigin.sh:10

Zincir içten dışa okunur: nerede çağrısı ic içinden geldi, ic çağrısı dis içinden, dis çağrısı üst düzeyden. Dizin kaydırması (BASH_LINENO[i-1]) bilinçlidir; üç dizi aynı olayın farklı yönlerini farklı hizalamalarla tutar.

Bu fonksiyon, önceki derste kurulan ERR kancasına eklendiğinde betik, hatanın yalnızca satırını değil çağrı yolunu da bildirir.

Bir Yöntem

Araçların sırası, hata ayıklamanın yöntemini de verir:

  1. bash -n ile sözdizimini geçirin.
  2. Katı kip başlığını ekleyin; hataların çoğu kendiliğinden görünür hâle gelir.
  3. Sorunun hangi bölümde olduğunu daraltın — bölümün sonuna geçici bir exit koyup ikiye bölmek en hızlı yoldur.
  4. Daraltılan bölümü set -x ile izleyin ve PS4 ile konum bilgisi ekleyin.
  5. Genişletmenin girdisi ile çıktısı arasında fark arıyorsanız -v ile birlikte açın.
  6. Karmaşık veri yapılarını declare -p ile yazdırın.

Bu sıra, en ucuz aracı en başa koyar. set -x ile başlanan bir oturum, bash -n’in bir saniyede bulacağı bir sözdizim hatasını aramakla geçebilir.

Özet

  • bash -n yalnızca sözdizimini denetler; çalıştırmaz ve mantık hatalarını görmez.
  • set -x her komutu genişletmelerden sonra yazar; tırnaklamanın doğru çalışıp çalışmadığını gösteren doğrudan kanıttır.
  • PS4 izleme önekini belirler ve her satır için yeniden genişletilir; tek tırnak içinde atanmalıdır.
  • BASH_XTRACEFD izleme çıktısını ayrı bir tanıtıcıya yollayarak betiğin kendi iletilerinden ayırır.
  • set -v yazıldığı hâli, set -x genişletilmiş hâli gösterir; birlikte kullanıldıklarında genişletmenin girdisi ve çıktısı karşılaştırılır.
  • declare -p dizi içeriğini sınırlarıyla birlikte, FUNCNAME/BASH_SOURCE/BASH_LINENO çağrı yığınını verir.

Sonraki Adım

Bu dersin araçları hatayı, ortaya çıktıktan sonra bulur. Kabuk betiklerindeki hataların büyük bölümü ise ortaya çıkmadan önce belirlidir: tırnaklanmamış genişletme, sınanmamış çıkış kodu, taşınabilir olmayan seçenek. Sonraki ders bu sınıfı çalıştırmadan yakalayan statik çözümleme aracını ele alır ve rapor.sh betiğini onun denetiminden geçirir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat