Ders 23 / 24
Statik Çözümleme
Çalıştırmadan hata bulma, yaygın kabuk hata sınıfları, yorumlayıcı bildirimine göre taşınabilirlik denetimi ve uyarı bastırmanın doğru kullanımı.
İçindekiler
Önceki 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, dosya listesini komut çıktısından üretme, yorumlayıcı bildiriminin izin vermediği bir özelliğin kullanılması.
Bu hatalar kodun kendisine bakılarak, çalıştırılmadan görülebilir. Statik çözümleme (static analysis), kaynak metni ayrıştırıp bilinen hatalı desenleri arayan yöntemdir. Kabuk için bu iş, betiği ayrıştırıp yüzlerce desen kuralını uygulayan bir araca devredilir.
Aracın değeri, bu kursta tek tek anlatılan tuzakların hepsini birden hatırlamasıdır.
Bir Denetim
Aşağıdaki betik, önceki derslerde ele alınan hataların çoğunu bir arada taşıyor:
#!/bin/sh kutuk=$1 for f in $(ls kutukler); do echo "islenen: $f" done if [ $kutuk = "erisim.log" ]; then adet=`wc -l < $kutuk` echo "satir: $adet" fi cd /tmp rm -rf $kutuk_yedek
Çözümleyicinin uzun biçimli çıktısı, bulguyu kaynak satırın altında işaretler ve düzeltilmiş yazımı önerir. Aynı bulgular tek satırlık biçimde daha derli toplu okunur:
shellcheck -f gcc kusurlu.sh
kusurlu.sh:3:10: error: Iterating over ls output is fragile. Use globs. [SC2045] kusurlu.sh:6:6: note: Double quote to prevent globbing and word splitting. [SC2086] kusurlu.sh:7:8: note: Use $(...) notation instead of legacy backticks `...`. [SC2006] kusurlu.sh:7:17: note: Double quote to prevent globbing and word splitting. [SC2086] kusurlu.sh:10:1: warning: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. [SC2164] kusurlu.sh:11:8: warning: kutuk_yedek is referenced but not assigned. [SC2154]
Beş ayrı hata sınıfı, on bir satırlık bir betikte. Sonuncusunun sonucu, set -u olmayan
bir betikte rm -rf komutunun argümansız çalışmasıdır.
Öntanımlı biçim daha ayrıntılıdır: her bulgunun altına Did you mean: başlığıyla
düzeltilmiş satırı yazar. Tek bir bulguyla uğraşırken bu biçim, toplu denetimde ise
yukarıdaki tek satırlık biçim uygundur.
Yakalanan Hata Sınıfları
Uyarılar dört öbeğe ayrılır ve her biri bu kursta ayrı bir dersin konusuydu.
Genişletme ve tırnaklama. Tırnaklanmamış değişken genişletmesi, "$@" yerine $*
kullanımı, dizi genişletmesinin yanlış biçimi. Değişkenler ve Tırnak Kuralları dersinin
tamamı bu sınıfa aittir. En sık görülen uyarı budur.
Çıkış kodu. Sınanmayan cd, sınanmayan komut yerine koyma, local ile birlikte atama
yapılması, boru hattı kodunun kaybolması. Hata Yönetimi dersindeki tuzaklar burada
yakalanır.
Kabuk deyimleri. Dosya listesini komut çıktısından üretme, read çağrısında -r
eksikliği, eski ters tırnak yazımı, [ ] içinde yanlış işleç. Döngüler ve Koşullar
derslerinin uyarıları.
Tanımsız ad. Hiç atanmamış bir değişkenin okunması, yazım yanlışıyla farklı bir ada dönüşen değişkenler. Bu sınıf, yorumlanan bir dilde çalışma anında bile fark edilmeyebilir.
Her uyarının bir kimliği vardır (SC ve dört rakam). Kimlik iki işe yarar: uyarının
ayrıntılı açıklamasına ulaşmak ve — gerektiğinde — o uyarıyı hedefli biçimde bastırmak.
Şiddet Düzeyleri
Uyarılar dört düzeye ayrılır: error, warning, info, style. -S seçeneği belirli
bir düzeyin altındakileri gizler:
shellcheck -S warning -f gcc kusurlu.sh
kusurlu.sh:3:10: error: Iterating over ls output is fragile. Use globs. [SC2045] kusurlu.sh:10:1: warning: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. [SC2164] kusurlu.sh:11:8: warning: kutuk_yedek is referenced but not assigned. [SC2154]
Üç bulgu kaldı; info ve style düzeyindeki tırnaklama ve ters tırnak uyarıları
gizlendi.
Bu, var olan bir kod tabanına çözümleme eklerken yararlıdır: önce error düzeyi
temizlenir, sonra eşik kademeli olarak düşürülür. Eşiği kalıcı olarak yüksek tutmak ise
aracın değerinin çoğunu bırakmak demektir — info düzeyindeki tırnaklama uyarıları en sık
gerçek hataya dönüşenlerdir.
Taşınabilirlik Denetimi
Çözümleyici, yorumlayıcı bildirimini okur ve kuralları ona göre uygular. #!/bin/sh
bildiren bir betikte bash eklentileri hata sayılır:
#!/bin/sh kodlar=(200 404) if [[ ${#kodlar[@]} -gt 1 ]]; then echo "birden cok kod" fi
In tasinabilir.sh line 2:
kodlar=(200 404)
^-------^ SC3030 (warning): In POSIX sh, arrays are undefined.
In tasinabilir.sh line 3:
if [[ ${#kodlar[@]} -gt 1 ]]; then
^-----------------------^ SC3010 (warning): In POSIX sh, [[ ]] is undefined.
Bu, Betik Anatomisi dersindeki kuralın araçla uygulanmasıdır: POSIX bildirip bash özelliği kullanmak, betiğin başka bir sistemde sessizce bozulmasına yol açar. Aynı betik bash bildirdiğinde bu iki uyarı üretilmez.
Denetimin sınırı da buradan görülür: araç, bildirilen kabuğa göre karar verir. Bildirim yanlışsa denetim de yanlış yapılır. Bu yüzden yorumlayıcı bildirimi, betiğin gerçekten kullandığı özelliklere göre yazılmalıdır.
Uyarı Bastırma
Bir uyarının bilinçli olarak yok sayılması gerektiğinde, kaynak koda bir yönerge yazılır:
#!/usr/bin/env bash # shellcheck disable=SC2086 kutuk="a b.log" echo $kutuk
Yönerge, kendisinden sonraki komuta uygulanır; dosyanın başına, ilk komuttan önce konursa tüm dosyayı kapsar.
Bastırma üç kurala bağlıdır.
Kimlik verilir. disable yönergesi kimliksiz yazılabilir ama o zaman tüm uyarıları
kapatır ve gelecekte oluşacak gerçek hataları da gizler.
Kapsam daraltılır. Dosya düzeyinde bastırma, o dosyadaki her satırı denetimden çıkarır. Uyarının doğduğu satırın hemen üstüne yazmak yeğlenir.
Gerekçe yazılır. Bastırılan uyarının neden yanlış alarm olduğu bir yorumla belirtilir. Gerekçesiz bir bastırma, kodu okuyan bir sonraki kişi için “burada ne olduğunu bilmiyorum” demektir.
Kelime bölmenin gerçekten istendiği durumlarda daha iyi bir çözüm vardır: değeri bir diziye
almak. "${dizi[@]}" yazımı hem uyarı üretmez hem de niyeti kodda görünür kılar. Bastırma,
yeniden yazmanın mümkün olmadığı hâllere bırakılır.
Betiği Denetimden Geçirmek
rapor.sh bu kurs boyunca, çözümleyicinin uyaracağı desenlerden kaçınılarak yazıldı.
Aşağıdaki küçük betik aynı kuralları izler:
#!/usr/bin/env bash set -Eeuo pipefail kutuk="${1:-erisim.log}" [ -r "$kutuk" ] || { echo "okunamiyor: $kutuk" >&2; exit 3; } adet=$(grep -c '' "$kutuk") printf 'satir: %d\n' "$adet"
shellcheck temiz.sh; echo "kod=$?"
kod=0
Çıkış kodu 0 ve hiçbir çıktı yok. Araç, bulgu varsa sıfırdan farklı kod döndürür; bu,
denetimi bir doğrulama adımına dönüştürmeyi mümkün kılar:
shellcheck rapor.sh && bash -n rapor.sh && echo "denetim gecti"
Sürüm denetim sistemine gönderim öncesinde ya da bir sürekli tümleştirme adımında bu komutun çalıştırılması, kabuk betiklerinin kalitesini korumanın en ucuz yoludur.
Statik Çözümlemenin Sınırı
Araç, kodun anlamını değil biçimini görür. Yakalayamadıkları:
- Yanlış alan numarası (
$7yerine$8yazmak). - Yanlış düzenli ifade (kalıp geçerli ama beklenen kümeyi betimlemiyor).
- Yanlış iş mantığı (yüzdenin yanlış toplama bölünmesi).
- Çalışma ortamına bağlı hatalar (var olmayan komut, yetersiz izin).
Bu sınıflar için sınama gerekir: betiği bilinen bir girdiyle çalıştırıp çıktısını beklenen
çıktıyla karşılaştırmak. Bu kursun erisim.log dosyasının sabit ve yeniden üretilebilir
olması, tam olarak böyle bir sınamayı mümkün kılmak içindir.
Statik çözümleme sınamanın yerini tutmaz; sınamanın bulacağı hata sayısını azaltır.
Özet
- Statik çözümleme, betiği çalıştırmadan ayrıştırıp bilinen hatalı desenleri arar.
- Yakalanan başlıca sınıflar tırnaklama, çıkış kodu, kabuk deyimleri ve tanımsız adlardır; hepsi bu kursta ayrı derslerin konusuydu.
- Denetim, yorumlayıcı bildirimine göre yapılır; POSIX bildiren betikte bash eklentileri uyarı üretir.
- Bastırma kimlikle, dar kapsamla ve gerekçeyle yapılır; kelime bölme isteniyorsa doğru çözüm dizi kullanmaktır.
- Araç bulgu varsa sıfırdan farklı kod döndürür; bu, denetimi bir doğrulama adımına çevirir.
- Statik çözümleme biçimi görür, anlamı görmez; yanlış alan numarası ve yanlış iş mantığı ancak sınamayla bulunur.
Sonraki Adım
Betik doğru, sağlam ve denetlenmiş durumda — ama hâlâ elle çalıştırılıyor. Bir raporun değeri düzenli üretilmesindedir. Son ders zamanlanmış görevleri ele alır: süreli görev tanımının okunması, zamanlayıcı ortamının etkileşimli kabuktan hangi noktalarda ayrıldığı, çıktının nereye yazılacağı ve üst üste binen çalışmaların engellenmesi.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.