2 puan yazan GN⁺ 2024-03-03 | 1 yorum | WhatsApp'ta paylaş
  • 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.sh ile çalıştırmak, script.sh dosyasının başına set -x eklemekle aynı etkiyi yaratır
  • DEBUG trap'i ile read birlikte kullanıldığında her satır çalıştırılmadan önce durup dosya adını, satır numarasını ve sıradaki komutu kontrol edebilirsiniz
  • die() { 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 -x ekleyerek kullanılabilir
  • Aynı davranış komut satırından da elde edilebilir
    • $ bash -x script.sh
    • Bu, script.sh dosyasının en üstüne set -x eklemekle aynıdır

Her satırda durup kontrol etmek

  • DEBUG trap'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")' DEBUG
    • read -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

  • die fonksiyonu, bir komut başarısız olduğunda mesaj yazdırıp programı sonlandırmak için kullanılabilir
    • die() { 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 1 ile sonlanır

1 yorum

 
GN⁺ 2024-03-03
Hacker News yorumları
  • ZFSBootMenu'de hata ayıklamaya yardımcı olmak için birkaç iyi özel fonksiyon kullanılıyor
    Kodun çeşitli yerlerinde zdebug gü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 -x kullanırken PS4='+ ${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
  • shellcheck de tavsiye ediliyor. Sorunu doğrudan yakalayamasa bile potansiyel sorunları gösteriyor
    Ayrı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
    • Katılıyorum ama bunu CI/CD mühendisliği ve YAML pipeline tarafına da söylemek lazım
  • Çıkış kodlarını şu şekilde kullanmak hata ayıklamayı daha da iyileştirebilir
    die(), hata mesajını standart hataya yazdırıp belirtilen hata koduyla çıkan yardımcı bir fonksiyon
    Daha fazla kabuk betiği çıkış kodu ve yardımcı fonksiyon burada: https://github.com/SixArm/unix-shell-script-kit/blob/main/un...
    • İyi bir liste; deneyimli kullanıcıların çoğunun kendine ait yardımcı fonksiyonları vardır muhtemelen
      Yine de o die yaklaşımıyla ilgili küçük bir çekincem var. die fonksiyonu varsayılan olarak başarısız olan komutun çıkış kodunu aktarmalı ve o komutun hata çıktısını da gizlememeli
      Büyük bir betikte komut başarısızlığına kendim özel bir anlam yüklemek istersem daha özelleşmiş bir die kullanırım. Benim die iş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ır
  • Çok sayıda Bash fonksiyonu kullanıyorsanız bir tür stack trace oluşturmak da mümkün
    Bunun bir uygulama örneği burada: https://github.com/TritonDataCenter/sdc-headnode/blob/master...
    • Bir başka stack trace uygulaması da var: https://github.com/runag/runag/blob/main/lib/fail.sh
      some-command || fail "message" şeklinde kullanıldığında some-command sıfır olmayan bir çıkış durumu döndürürse stack trace oluşturup kabuğu kapatıyor
      Bir fonksiyon içinden stack trace üretip dönmek isterseniz some-command || softfail "message" || return $? gibi kullanılabiliyor
  • Bash'in hâlâ fiili kabuk betik dili olmasının eski sistem mirası ve ataleti dışında bir sebebi olup olmadığını merak ediyorum
    Gerekli 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
    • Eski kullanımın popülerlikte büyük payı olduğu doğru
      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
    • Bourne kabuk betikleme yeterince iyi olduğu için yerine bir şey koymak neredeyse imkânsız
      Plan 9'un rc kabuğ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ştirmiyor
      Yerleş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
    • Bunun gerçekten eski sistemler ve atalet yüzünden olduğunu düşünüyorum

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 gerekir
Bash/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ır
Tasarı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

  • Bunun gerçekten Bash olduğundan emin değilim
    FreeBSD'deki betiklerin çoğu sh için yazılmıştır ve sh, 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üyorum
  • Her yerde bulunması büyük bir avantaj
    Ama 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
  • Oldukça güçlü bir gdb tarzı gerçek hata ayıklayıcı da var: https://bashdb.sourceforge.net/
  • Biraz ilgili bir tanıtım yapacak olursam, geçmişte ara çıktıları koruyan bir Bash pipeline hata ayıklayıcısı yapmıştım
    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çinde exit yapmaya ç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" ... done gibi bir pipeline içinde die çağırırsanız, sonrasındaki echo "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çindeki die sorununu da çözer
    Ama 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ıp die() { echo "$1" >&2; kill -9 $MYPID; exit 1; } gibi öldürebilirsiniz
    Elbette bunun da ödünleri var. Bu şekilde öldürmek oldukça serttir ve nedenini bilmiyorum ama tamamen güvenilir de görünmüyordu
    • Sadece set -e eklemek bile alt kabuk sıfır olmayan bir hata koduyla çıktığında betiğin de kapanmasını sağlar
      Herhangi bir kabuk betiğinde set -e kullanmamak için iyi bir sebep aklıma gelmiyor
    • PID'yi bu şekilde öldürmek zombi süreçler oluşturmaz mı?
  • Bash betiklerinin en üstüne her zaman set -euxo pipefail koyarım
    Koşul testlerini biraz daha zorlaştırır ama özellikle pipefail tek başına bile defalarca çok işime yaradı
    • https://mywiki.wooledge.org/BashPitfalls#set_-euo_pipefail
    • Bunu birden fazla satır için açıp sonra set +x ile kapatabilirsiniz
      Sürekli açık bırakmak epey sıkıcı oluyor
    • Hayat kurtaran bir ayar
      Yine de -x seçeneğini, gerçekten tüm dağınık hata ayıklama çıktısını görmem gerekene kadar saklı tutuyorum