2 poin oleh GN⁺ 2024-03-03 | 1 komentar | Bagikan ke WhatsApp
  • Saat skrip Bash berjalan tidak sesuai harapan, melacak penyebabnya jadi lebih mudah hanya dengan melihat perintah yang benar-benar dijalankan
  • set -x menampilkan setiap baris setelah ekspansi variabel, sehingga Anda bisa memeriksa perintah apa yang sebenarnya dijalankan skrip
  • Menjalankannya dari baris perintah dengan bash -x script.sh memberi efek yang sama seperti menambahkan set -x di bagian atas script.sh
  • Menggunakan trap DEBUG bersama read memungkinkan Anda berhenti sebelum setiap baris dijalankan untuk memeriksa nama file, nomor baris, dan perintah berikutnya
  • Fungsi die() { echo $1 >&2; exit 1; } menyederhanakan alur menampilkan pesan ke standard error lalu keluar setelah perintah gagal

Memeriksa alur eksekusi secara visual

  • set -x menampilkan baris yang dijalankan skrip, dan variabel ditampilkan dengan nilainya yang sudah diekspansi
  • Anda bisa memakainya dengan menambahkan set -x di bagian atas skrip
  • Perilaku yang sama juga bisa dijalankan dari baris perintah
    • $ bash -x script.sh
    • Ini sama seperti menambahkan set -x di paling atas script.sh

Berhenti dan memeriksa setiap baris

  • trap DEBUG dijalankan sebelum setiap baris kode dieksekusi
  • Jika Anda menambahkan kode berikut di awal skrip, skrip akan menunggu input Enter sebelum menjalankan perintah berikutnya
    • trap '(read -p "\[$BASH_SOURCE: $LINENO] $BASH_COMMAND")' DEBUG
    • read -p menampilkan pesan dan menunggu input Enter
    • $BASH_SOURCE adalah nama file skrip
    • $LINENO adalah nomor baris
    • $BASH_COMMAND adalah perintah yang akan dijalankan berikutnya

Menampilkan pesan lalu keluar saat gagal

  • Fungsi die dapat digunakan untuk menampilkan pesan dan mengakhiri program saat perintah gagal
    • die() { echo $1 >&2; exit 1; }
    • Anda bisa menambahkannya setelah perintah yang dapat gagal, seperti some_command || die "oh no!"
  • Fungsi ini mengirim pesan ke standard error dan keluar dengan exit 1

1 komentar

 
GN⁺ 2024-03-03
Pendapat Hacker News
  • Di ZFSBootMenu, mereka memakai beberapa fungsi buatan sendiri yang cukup bagus untuk membantu debugging
    Ada fungsi logging zdebug di berbagai bagian kode: https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme...
    Jika logging debug dinyalakan lalu menekan Ctrl-T dari menu utama, akan muncul tampilan seperti ini: https://i.imgur.com/Ge75zkP.png
    Ada juga profiling flamegraph yang bisa diaktifkan lewat https://github.com/zbm-dev/zfsbootmenu/blob/master/zfsbootme..., dan jika data yang di-dump lewat port serial dirakit kembali, bisa dibuat grafik seperti https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master...
    Bash ternyata fleksibel juga
  • Saat memakai set -x, PS4='+ ${BASH_SOURCE:-}:${FUNCNAME[0]:-}:L${LINENO:-}: ' sangat berguna
    Dengan ini, nama file, nama fungsi, dan nomor baris ditampilkan, sehingga cukup membantu saat men-debug skrip Bash besar
  • Saya juga merekomendasikan shellcheck. Meski tidak menemukan masalah secara langsung, ia memberi tahu potensi masalah
    Saya juga menyarankan menulis ulang skrip dengan bahasa lain. Di perusahaan, kami sedang mengganti skrip Bash ke Rust; biaya masuknya memang tinggi, tetapi kode akhirnya jauh lebih mudah dirawat dan lebih andal
    Bash masih bagus untuk skrip cepat, tetapi kalau sudah melewati sekitar 100 baris, layak memakai bahasa yang memberi jaminan lebih kuat
    • Setuju, tapi pembicaraan itu juga perlu diarahkan ke rekayasa CI/CD dan pipeline YAML
  • Debugging bisa ditingkatkan lagi dengan memakai kode keluar seperti ini
    die() adalah fungsi pembantu yang mencetak pesan kesalahan ke standard error lalu keluar dengan kode kesalahan yang ditentukan
    Lebih banyak kode keluar skrip shell dan fungsi pembantu ada di sini: https://github.com/SixArm/unix-shell-script-kit/blob/main/un...
    • Daftar yang bagus, dan sepertinya pengguna berpengalaman masing-masing punya fungsi pembantu sendiri
      Namun saya agak kurang sreg dengan filosofi die itu. Fungsi die pada dasarnya seharusnya meneruskan kode keluar dari perintah yang gagal, dan tidak menyembunyikan output kesalahan dari perintah tersebut
      Kalau dalam skrip besar saya ingin memberi makna sendiri pada kegagalan perintah, saya akan memakai die yang lebih khusus secara terpisah. die saya kira-kira berbentuk __errex "$?" "${LINENO}" "$0", yang mencetak error fatal, nomor baris, nama skrip, dan pesan, lalu berakhir dengan kode keluar tersebut
  • Jika banyak memakai fungsi Bash, membuat semacam stack trace juga memungkinkan
    Salah satu contoh implementasinya ada di sini: https://github.com/TritonDataCenter/sdc-headnode/blob/master...
    • Ada implementasi stack trace lain: https://github.com/runag/runag/blob/main/lib/fail.sh
      Jika dipakai seperti some-command || fail "message", saat some-command mengembalikan status keluar bukan 0, ia akan membuat stack trace dan menghentikan shell
      Jika ingin membuat stack trace di dalam fungsi lalu mengembalikannya, bisa memakai some-command || softfail "message" || return $?
  • Saya penasaran apakah alasan Bash masih menjadi bahasa de facto untuk shell scripting, selain warisan lama dan inersia, memang ada
    Ia bisa melakukan hal yang dibutuhkan, tetapi kaku dan sintaksnya juga mengerikan. Begitu skrip mencapai ukuran atau kompleksitas tertentu, kita dipaksa memindahkannya ke bahasa yang benar-benar layak; mungkin itu memang desain yang disengaja
    • Benar bahwa pemakaian warisan lama menyumbang besar pada popularitasnya
      Di distro modern, umumnya ada versi Bash yang cukup baru, dan kalau tidak memakai hal seperti array, versi juga tidak terlalu perlu dipikirkan
      Daya tarik Bash ada pada posisinya di antara bahasa dan tool lain. Ia ideal untuk merangkai tool lain, cukup dekat dengan sistem operasi sehingga nyaman, dan tidak menuntut instalasi library seperti Python
      Untuk scripting yang lebih kompleks, orang sering menyarankan pindah ke bahasa seperti Python, tetapi itu menambahkan lapisan kompleksitas yang dalam jangka panjang bisa jadi tidak membantu. Skrip Bash yang ditulis 20 tahun lalu masih berjalan baik, sedangkan program Python dari 20 tahun lalu kemungkinan besar mengalami masalah versi
    • Scripting shell Bourne cukup baik, sehingga hampir mustahil digantikan
      rc dari Plan 9 memang lebih rapi, tetapi tidak ada yang akan pindah hanya karena “mirip tapi lebih rapi”. Saat ini juga Anda bisa memasang sesuatu yang mirip tetapi lebih baik dari https://pkgsrc.se/shells, tetapi orang tetap tidak memakainya, dan cara menjalankannya bagi orang lain pun tidak berubah
      Untuk menggantikan teknologi yang sudah mapan, ia harus beberapa kali lipat lebih baik dalam aspek-aspek inti. Plan 9 juga lebih baik daripada keluarga UNIX, tetapi tidak cukup baik untuk menggantikannya
      Sulit membuat sesuatu yang cukup baik untuk menggantikan ceruk scripting shell Bourne. Sebelum menjadi sebaik itu, ia sudah masuk ke posisi ekologis atau wilayah masalah bahasa scripting sungguhan seperti Perl, Python, atau Ruby
      Dalam wilayah masalah yang sempit, optimum lokal menyedot semua udara, sehingga sulit bagi pesaing yang mendekati optimum global teoretis untuk muncul
    • Saya benar-benar menganggap penyebabnya adalah warisan lama dan inersia

Fitur-fitur baru belakangan ini memang cukup baik ditumpangkan di atas sh/Bash, tetapi pada akhirnya scripting shell hanyalah sarana untuk mencapai tujuan, dan harus berevolusi jauh lebih lambat dibanding bahasa pemrograman umum
Ciri inti Bash/sh adalah sifatnya yang anti-entropi. Karena hampir tidak ada pengembangan atau evolusi, kecil kemungkinan kita dibuat pusing oleh dependensi atau fitur baru, dan hal-hal yang berfungsi 20 tahun lalu tetap menjadi tool dasar
Secara desain, ini menjadi sistem yang menolak perubahan, dan ketika orang mentok pada batasannya, muncul insentif untuk keluar dari situ

  • Tidak yakin itu benar-benar Bash
    Sebagian besar skrip FreeBSD ditulis untuk sh, dan karena sh adalah bagian dari standar POSIX, rasanya dukungannya jauh lebih luas. Menurut saya Bash hanya termasuk populer
  • Keberadaannya di mana-mana itu faktor besar
    Namun karena Bash terlalu buruk, saya sampai membuat banyak utilitas dengan namespace yang dipersempit agar bisa memakai skrip Groovy. Bisa dikembangkan dengan IDE, sistem pustakanya juga aman, dan Groovy merapikan hampir semua ketidaknyamanan Java, jadi jauh lebih baik
  • Ada juga debugger sungguhan bergaya gdb yang cukup kuat: https://bashdb.sourceforge.net/
  • Sedikit promosi terkait: dulu saya membuat debugger pipeline Bash yang mempertahankan output perantara
    Ada beberapa keterbatasan, tetapi secara umum bisa berguna: https://github.com/ketancmaheshwari/pd
  • Teknik die() bagus, tetapi Bash punya sifat yang menyebalkan. Jika mencoba melakukan exit di dalam subshell, yang berhenti hanya subshell itu, sementara sisa skrip tetap berjalan
    Misalnya, jika memanggil die di dalam pipeline seperti cat myfile | while read line; do ... die "Found match" ... done, echo "I don't want this line" setelahnya tetap akan tercetak
    Sering kali subshell bisa dihindari, dan pada contoh ini shellcheck memang tepat menunjuk UUOC; memperbaikinya juga menyelesaikan masalah die di dalam subshell
    Namun kadang subshell tidak bisa dihindari, atau menghindarinya membuat skrip terlalu rumit. Dalam kasus seperti itu, Anda bisa menyimpan PID di awal skrip dengan MYPID=$$, lalu mematikannya seperti die() { echo "$1" >&2; kill -9 $MYPID; exit 1; }
    Tentu ini juga kompromi. Cara mematikan seperti ini cukup kasar, dan entah kenapa juga tidak sepenuhnya dapat diandalkan
    • Menambahkan set -e saja sudah membuat skrip ikut berhenti ketika subshell keluar dengan kode error bukan 0
      Saya sulit membayangkan alasan untuk tidak memakai set -e dalam skrip shell apa pun
    • Kalau mematikan PID seperti itu, bukankah bisa muncul proses zombie?
  • Di bagian paling atas skrip Bash, saya selalu menaruh set -euxo pipefail
    Pengujian kondisi memang jadi sedikit lebih sulit, tetapi terutama pipefail saja sudah berkali-kali membayar dirinya
    • https://mywiki.wooledge.org/BashPitfalls#set_-euo_pipefail
    • Bisa juga dinyalakan untuk beberapa baris lalu dimatikan dengan set +x
      Kalau terus dinyalakan, cukup membosankan
    • Ini pengaturan penyelamat nyawa
      Namun -x saya simpan sampai benar-benar perlu melihat semua output debug yang berantakan