Bash Hata Ayıklama
(wizardzines.com)- Bash betiği beklenenden farklı çalıştığında, çalıştırılan komutları olduğu gibi görmek bile nedenin izini sürmeyi kolaylaştırır
set -x, her satırı değişkenler genişletildikten sonra yazdırır; böylece betiğin gerçekte hangi komutları çalıştırdığını görmenizi sağlar- Komut satırında
bash -x script.shile çalıştırmak,script.shdosyasının başınaset -xeklemekle aynı etkiyi yaratır DEBUGtrap'i ilereadbirlikte kullanıldığında her satır çalıştırılmadan önce durup dosya adını, satır numarasını ve sıradaki komutu kontrol edebilirsinizdie() { echo $1 >&2; exit 1; }fonksiyonu, başarısız olan komutun sonuna eklenerek standart hata çıktısına mesaj yazıp çıkma akışını basitleştirir
Çalışma akışını gözle doğrulamak
set -x, betiğin çalıştırdığı satırları yazdırır ve değişkenleri genişletilmiş değerleriyle gösterir- Betiğin başına
set -xekleyerek kullanılabilir - Aynı davranış komut satırından da elde edilebilir
$ bash -x script.sh- Bu,
script.shdosyasının en üstüneset -xeklemekle aynıdır
Her satırda durup kontrol etmek
DEBUGtrap'i, her kod satırı çalıştırılmadan önce devreye girer- Betiğin başlangıcına aşağıdaki kod eklenirse, bir sonraki komut çalıştırılmadan önce Enter tuşuna basılması beklenir
trap '(read -p "\[$BASH_SOURCE: $LINENO] $BASH_COMMAND")' DEBUGread -p, bir mesaj yazdırır ve Enter girişini bekler$BASH_SOURCE, betik dosyasının adıdır$LINENO, satır numarasıdır$BASH_COMMAND, sıradaki çalıştırılacak komuttur
Hata durumunda mesaj bırakıp çıkmak
diefonksiyonu, bir komut başarısız olduğunda mesaj yazdırıp programı sonlandırmak için kullanılabilirdie() { echo $1 >&2; exit 1; }- Başarısız olabilecek bir komutun sonuna
some_command || die "oh no!"şeklinde eklenebilir
- Bu fonksiyon mesajı standart hata çıktısına gönderir ve
exit 1ile sonlanır
1 yorum
Hacker News yorumları
Kodun çeşitli yerlerinde
zdebuggünlükleme fonksiyonu bulunuyor: https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme...Debug günlükleme açıkken ana menüde Ctrl-T'ye basınca şöyle bir ekran çıkıyor: https://i.imgur.com/Ge75zkP.png
Ayrıca https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme... ile açılabilen flamegraph profilleme de var; seri port üzerinden dökülen veriler yeniden birleştirilirse https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master... gibi bir grafik üretilebiliyor
Bash beklenmedik derecede esnek
set -xkullanırkenPS4='+ ${BASH_SOURCE:-}:${FUNCNAME[0]:-}:L${LINENO:-}: 'çok faydalıBöylece dosya adı, fonksiyon adı ve satır numarası gösteriliyor; bu da büyük Bash betiklerini hata ayıklarken epey yardımcı oluyor
shellcheckde tavsiye ediliyor. Sorunu doğrudan yakalayamasa bile potansiyel sorunları gösteriyorAyrıca betiği başka bir dilde yeniden yazmak da öneriliyor. Şirkette Bash betiklerini Rust'a çeviriyoruz; giriş maliyeti yüksek olsa da ortaya çıkan kod çok daha kolay bakım yapılabilir ve daha güvenilir oluyor
Bash hızlı betikler için hâlâ iyi, ama yaklaşık 100 satırı geçince daha güçlü garantiler sunan bir dil kullanmaya değer
die(), hata mesajını standart hataya yazdırıp belirtilen hata koduyla çıkan yardımcı bir fonksiyonDaha fazla kabuk betiği çıkış kodu ve yardımcı fonksiyon burada: https://github.com/SixArm/unix-shell-script-kit/blob/main/un...
Yine de o
dieyaklaşımıyla ilgili küçük bir çekincem var.diefonksiyonu varsayılan olarak başarısız olan komutun çıkış kodunu aktarmalı ve o komutun hata çıktısını da gizlememeliBüyük bir betikte komut başarısızlığına kendim özel bir anlam yüklemek istersem daha özelleşmiş bir
diekullanırım. Benimdieişlevim kabaca__errex "$?" "${LINENO}" "$0"biçiminde ölümcül hata, satır numarası, betik adı ve mesajı yazdırıp o çıkış koduyla sonlanırBunun bir uygulama örneği burada: https://github.com/TritonDataCenter/sdc-headnode/blob/master...
some-command || fail "message"şeklinde kullanıldığındasome-commandsıfır olmayan bir çıkış durumu döndürürse stack trace oluşturup kabuğu kapatıyorBir fonksiyon içinden stack trace üretip dönmek isterseniz
some-command || softfail "message" || return $?gibi kullanılabiliyorGerekli işleri yapabiliyor ama hantal ve sözdizimi de korkunç. Betik belli bir boyut veya karmaşıklığa ulaşınca insanı düzgün bir dile geçmeye zorlaması, belki de bilinçli bir tasarım tercihi olabilir diye düşündürüyor
Modern dağıtımlarda genelde oldukça güncel bir Bash sürümü bulunuyor ve dizi gibi şeyler kullanmayacaksanız sürümü çok da dert etmeniz gerekmiyor
Bash'in çekiciliği diğer diller ve araçlar arasındaki konumunda yatıyor. Başka araçları birbirine bağlamak için ideal ve işletim sistemine yeterince yakın olduğu için kullanışlı; ayrıca Python gibi kütüphane kurulumu da gerektirmiyor
Daha karmaşık betiklerin Python gibi dillere taşınması sık öneriliyor ama bu, uzun vadede faydalı olmayabilecek ek bir karmaşıklık katmanı getirebiliyor. 20 yıl önce yazılmış bir Bash betiği hâlâ sorunsuz çalışabilirken, 20 yıllık bir Python programı büyük olasılıkla sürüm sorunları yaşar
Plan 9'un
rckabuğu biraz daha temiz ama “benzer ama daha temiz” bir şey için kimse geçiş yapmıyor. Şu anda bile https://pkgsrc.se/shells üzerinden benzer ama daha iyi seçenekler kurabilirsiniz, ama insanlar kullanmıyor ve başkalarına da çalışma biçimini değiştirmiyorYerleşmiş bir teknolojinin yerini almak için temel yönlerde birkaç kat daha iyi olmak gerekir. Plan 9 da UNIX türevlerinden daha iyi olabilir ama onların yerini alacak kadar iyi değildi
Bourne kabuk betiklemenin nişini dolduracak kadar iyi bir şey yapmak zor. O noktaya gelmeden önce zaten Perl, Python, Ruby gibi gerçek betik dillerinin ekolojik konumuna ya da problem alanına geçmiş oluyorsunuz
Dar bir problem alanında yerel optimum, teorik küresel optimuma daha yakın rakiplerin ortaya çıkmasını zorlaştıracak kadar bütün alanı kaplıyor
Yakın zamanda eklenen özellikler
sh/Bash üzerine iyi oturmuş olsa da, sonuçta kabuk betikleme bir amaç değil araçtır ve genel amaçlı programlama dillerine göre çok daha yavaş evrilmesi gerekirBash/
sh'nin temel özelliği anti-entropik olmasıdır. Neredeyse hiç geliştirme ya da evrim olmadığı için bağımlılıklar veya yeni özellikler yüzünden baş ağrıtma olasılığı düşüktür ve 20 yıl önce çalışan şeyler hâlâ temel araç olarak kalırTasarımı gereği değişime dirençli bir sistem olur ve sınırlara çarpıldığında insanları onun dışına çıkmaya yönelten bir teşvik yaratır
FreeBSD'deki betiklerin çoğu
shiçin yazılmıştır vesh, POSIX standardının bir parçası olduğu için çok daha geniş desteklendiğini düşünüyorum. Bash'in sadece popüler tarafta olduğunu düşünüyorumAma Bash o kadar kötüydü ki Groovy betikleri kullanabilmek için ad alanını küçültülmüş tonla yardımcı araç yaptım. IDE ile geliştirilebiliyordu, kütüphane sistemi güvenliydi ve Groovy, Java'nın hantallığının neredeyse tamamını törpülediği için çok daha iyiydi
Bazı kısıtları var ama genel olarak faydalı olabilir: https://github.com/ketancmaheshwari/pd
die()tekniği iyi ama Bash'in can sıkıcı bir özelliği var. Bir alt kabuk içindeexityapmaya çalışırsanız yalnızca alt kabuk kapanır ve betiğin geri kalanı çalışmaya devam ederÖrneğin
cat myfile | while read line; do ... die "Found match" ... donegibi bir pipeline içindedieçağırırsanız, sonrasındakiecho "I don't want this line"yine de yazdırılırÇoğu durumda alt kabuktan kaçınılabilir ve bu örnekte
shellcheck'in UUOC uyarısı yerindedir; onu düzeltmek alt kabuk içindekidiesorununu da çözerAma bazen alt kabuktan kaçınmak mümkün olmaz ya da kaçınmak betiği fazla karmaşıklaştırır. Böyle durumlarda betiğin başında
MYPID=$$ile PID'yi saklayıpdie() { echo "$1" >&2; kill -9 $MYPID; exit 1; }gibi öldürebilirsinizElbette bunun da ödünleri var. Bu şekilde öldürmek oldukça serttir ve nedenini bilmiyorum ama tamamen güvenilir de görünmüyordu
set -eeklemek bile alt kabuk sıfır olmayan bir hata koduyla çıktığında betiğin de kapanmasını sağlarHerhangi bir kabuk betiğinde
set -ekullanmamak için iyi bir sebep aklıma gelmiyorset -euxo pipefailkoyarımKoşul testlerini biraz daha zorlaştırır ama özellikle
pipefailtek başına bile defalarca çok işime yaradıset +xile kapatabilirsinizSürekli açık bırakmak epey sıkıcı oluyor
Yine de
-xseçeneğini, gerçekten tüm dağınık hata ayıklama çıktısını görmem gerekene kadar saklı tutuyorum