23 poin oleh GN⁺ 2025-12-31 | 1 komentar | Bagikan ke WhatsApp
  • Memperkenalkan trik untuk menjalankan file Go langsung seperti executable
  • Jika menaruh //usr/local/go/bin/go run "$0" "$@"; exit di baris pertama dan memberi izin eksekusi, file dapat dijalankan dengan ./script.go
  • Cara ini bukan shebang, melainkan memanfaatkan perilaku fallback POSIX ke /bin/sh saat ENOEXEC terjadi
  • Shell mengeksekusi baris pertama sebagai perintah, sementara kompiler Go mengenalinya sebagai komentar // dan mengabaikannya
  • Dengan "$0" path dirinya sendiri diteruskan sehingga go run membangun dan menjalankan skrip tersebut, dan $@ meneruskan argumen
  • Pustaka standar Go yang kuat dan jaminan kompatibilitas mundur membuatnya cocok untuk scripting; selama memakai Go 1.x, skrip dapat tetap berjalan selama puluhan tahun
  • Dapat menghindari kerumitan manajemen dependensi seperti virtual environment Python, pip/poetry/uv, dan sejenisnya

Cara kerja shebang palsu

  • shebang (#!) menentukan interpreter melalui system call execve, tetapi teknik yang diperkenalkan di tulisan ini bukan shebang
  • Baris pertama file sumber Go diisi dengan //usr/local/go/bin/go run "$0" "$@"; exit, lalu di bawah package main diletakkan kode Go biasa
    • Setelah memberi izin eksekusi dengan chmod +x script.go, file bisa dijalankan seperti ./script.go
  • Jika diperiksa dengan strace, saat shell mencoba menjalankan ./script.go lewat execve, kernel mengembalikan ENOEXEC (Exec format error)
    • Shell yang menerima ENOEXEC lalu fallback ke /bin/sh untuk menafsirkan file tersebut sebagai shell script
    • Di shell, // bukan komentar melainkan ditafsirkan sebagai path root (/), sehingga //usr/local/go/bin/go tetap merupakan path yang valid
  • Karena itu, baris pertama //usr/local/go/bin/go run "$0" "$@"; exit dijalankan sebagai perintah oleh shell
    • "$0" meneruskan path file yang dieksekusi, jadi dalam perintah tersebut "$0" menjadi path script.go, dan go run pun menemukan, membangun, lalu menjalankan dirinya sendiri
    • "$@" adalah ekspansi positional parameter mulai dari argumen pertama, sehingga pemanggilan seperti ./script.go -f flag0 here are some args bisa dilakukan
    • Tanpa ; exit, sh akan terus menafsirkan file Go baris demi baris lalu gagal saat menemui token seperti package

Mengapa Go cocok untuk scripting

  • Jaminan kompatibilitas mundur Go adalah fitur kunci, sehingga selama memakai Go 1.x, skrip yang ditulis bisa berjalan dalam jangka panjang
  • Pustaka standar yang matang dan tool bawaan (formatter, linter, dll.) tersedia tanpa konfigurasi tambahan, sehingga berbagi skrip dan portabilitas menjadi maksimal
    • Berbeda dengan Python, Anda tidak perlu mempelajari virtual environment atau berbagai package manager seperti pip, poetry, uv hanya untuk menjalankan kode
    • Dengan tool bawaan ekosistem Go dan integrasi IDE, formatter dan linter dapat dipakai secara default tanpa .pyproject atau package.json
  • Selama Go versi modern terpasang, skrip dapat berjalan di OS mana pun selama puluhan tahun

Perbandingan dengan bahasa terkompilasi lain

  • Rust memiliki waktu kompilasi yang lambat dan pustaka standar yang lebih lemah sehingga penggunaan dependensi nyaris wajib, serta tuntutan kesempurnaan yang memperlambat pengembangan
  • Java dan bahasa JVM sudah memiliki bahasa scripting berbasis bytecode JVM, dan lightweight Kotlin scripting juga bisa menjadi alternatif
  • Di antara bahasa terkompilasi, Go memiliki karakteristik yang paling cocok untuk kebutuhan scripting

Masalah formatting gopls dan solusinya

  • gopls mewajibkan spasi setelah komentar (//example// example), sehingga baris shebang palsu menjadi rusak
  • Jika ada spasi, // usr/local/go/bin/go tidak lagi dikenali shell sebagai path
  • Solusi: gunakan saran dari thread HN ini, yaitu komentar blok /**/ alih-alih //
    • Tulis dalam bentuk /*usr/local/go/bin/go run "$0" "$@"; exit; */
    • Titik koma (;) setelah exit wajib ada

1 komentar

 
GN⁺ 2025-12-31
Komentar Hacker News
  • Bagian “tidak peduli soal pip vs poetry vs uv” yang disebut penulis sebenarnya didukung langsung oleh uv
    Termasuk dependensi PyPI, asalkan versi Python dan uv terpasang, itu sudah cukup
    Tautan dokumentasi resmi uv

    • Bahkan ada cara yang lebih baik
      #!/usr/bin/env -S uv run --python 3.14 --script
      Dengan begini, meskipun Python sendiri tidak terpasang, uv akan mengunduh versi yang ditentukan lalu menjalankannya
    • Saya juga berpikir begitu, tetapi bagi pengguna non-Python, ini masih belum intuitif
      Saat pertama kali mencoba Clojure, kebanyakan orang disarankan memakai Leiningen, tetapi kalau mencari Python, yang muncul justru venv, poetry, hatch, uv, dan banyak lagi
      uv memang makin menjadi arus utama, tetapi belum benar-benar universal
      Saya pernah memasang Go lewat apt lalu harus memasangnya ulang karena versinya terlalu lama, tetapi itu jauh lebih cepat terselesaikan
      Masalah virtual environment di Python tetap rumit
    • Saya menyelesaikan masalah ini dengan PyFlow pada 2019
      Itu adalah alat OSS yang ditulis dengan Rust dan mengelola versi Python serta venv secara otomatis
      Tinggal atur pyproject.toml lalu jalankan pyflow main.py, maka ia akan memasang dan mengunci dependensi seperti Cargo, sekaligus otomatis menyesuaikan versi Python yang cocok untuk proyek
      Saat itu Poetry dan Pipenv populer, tetapi keduanya masih kurang dalam hal venv dan manajemen versi
    • Saya juga sudah banyak beralih ke uv
      Biasanya saya memakai uv add, dan hanya memakai uv pip saat diperlukan
      Tetapi uv pip tetap mewarisi keterbatasan pip — resolusi dependensi bisa berubah tergantung urutan instalasi
      Memasang dep-a dulu lalu dep-b, membalik urutannya, atau memasang keduanya sekaligus bisa memberi hasil berbeda
      Ini lebih merupakan masalah pip, tetapi kekacauan manajemen paket Python masih tetap ada
    • Sebenarnya bahkan tidak perlu menentukan versi Python
      uv akan mengunduhnya sendiri
  • Go secara eksplisit menolak dukungan shebang
    Sebagai gantinya, yang direkomendasikan adalah memakai gorun
    Bisa dijalankan dengan trik POSIX seperti /// 2>/dev/null ; gorun "$0" "$@" ; exit $?
    Nim, Zig, dan D bisa dipakai dengan cara serupa lewat opsi -run, sedangkan Swift, OCaml, dan Haskell dapat mengeksekusi file secara langsung
    Tautan diskusi terkait

    • Untuk skrip kecil, interpreter yaegi mungkin lebih baik daripada go run
      yaegi GitHub
  • Tulisan yang bilang “saya tidak mau tahu perbedaan pip, poetry, uv, saya cuma ingin menjalankan kodenya” pada akhirnya adalah soal tingkat kemahiran teknis
    uv run dan PEP 723 sebenarnya sudah menyelesaikan semua masalah

    • Betul, tetapi butuh waktu terlalu lama sampai uv run muncul
      Saya sudah memakai Python lebih dari 20 tahun, tetapi codebase dengan paket eksternal atau venv selalu terasa menakutkan
      Berkat uv run, semua proyek kantor sudah saya pindahkan, tetapi proyek pribadi sudah telanjur berpindah ke Go
      Dalam jangka panjang saya lebih suka bahasa bertipe statis
    • Kalau bahasanya sudah tua, pada akhirnya kita memang harus mempelajari pustaka-pustaka yang saling bersaing
    • Ini adalah masalah UX
      Pengguna cuma ingin programnya berjalan
      uv run dan PEP 723 memang menyelesaikan masalahnya, tetapi tetap ada hambatan masuk karena pengguna harus tahu soal uv
      Selama uv bukan alat default resmi, banyak pengguna akan meninggalkan Python
  • Menurut saya ini ide yang benar-benar brilian
    Namun scripting membutuhkan ergonomi yang berbeda dari perangkat lunak untuk distribusi
    bash itu improvisasional, Go cocok untuk diproduktisasi, Python ada di tengah-tengah, Ruby lebih dekat ke bash, dan Rust lebih dekat ke Go
    Skrip berguna saat kita perlu cepat menggabungkan perintah OS untuk menangani pekerjaan sekali pakai
    Go kurang punya sifat improvisasional itu

    • Saya juga setuju dengan pendapat bahwa Python itu “ada di tengah-tengah”
      Saya mencoba menjalankan aplikasi gtk sederhana di Debian dengan uv, dependensinya sudah benar tetapi tetap tidak jalan dan akhirnya Core Dump
      Hal seperti ini terulang setiap kali saya mencoba Python lagi
      Go memang verbose, tetapi setelah dikompilasi, ia tinggal berjalan saja
    • Saya juga merasakan hal serupa
      Intinya adalah apakah semuanya bisa selesai dalam satu file
      Skrip 500 baris masih mungkin dibuat di Go, tetapi bahasanya sendiri memang mengasumsikan banyak file dan modul
      Tidak didukungnya bang-line juga karena alasan itu
      Toh kalau go run pada akhirnya membuat biner sementara, menurut saya lebih baik sekalian build lalu taruh di /usr/local/bin
    • Mengatakan bahwa bash lebih dekat ke perintah OS itu keliru
      bash juga hanyalah lapisan abstraksi di atas OS, sama seperti Python, hanya saja terasa begitu karena ia shell bawaan
    • Di era ketika LLM bisa mengubah kode untuk kita, mungkin keterbacaan akan menjadi lebih penting daripada ergonomi penulisan
      Terutama ke arah membuat kode yang ditulis LLM lebih mudah dibaca manusia
  • Saya setuju bahwa pengguna yang baru mengenal Python tidak perlu tahu perbedaan antara pip, poetry, dan uv
    Tetapi kalau seseorang menulis artikel tentang topik ini, setidaknya dia harus tahu bahwa uv menyelesaikan masalah tersebut
    Kritik yang lahir dari ketidaktahuan tidak meyakinkan

    • Ada pertanyaan apakah uv juga menyelesaikan model “write once, run anywhere” seperti Go
      Saya juga penasaran karena belum sepenuhnya memahami konsep uv
  • Saya suka menulis skrip dengan Python
    Saya bisa bekerja cepat, dan untuk tugas sederhana enak karena tidak perlu terlalu memikirkan tipe atau memori
    Tetapi saya tidak ingin memakainya untuk aplikasi besar

    • Saya juga suka scripting Python, tetapi tidak suka memasang skrip buatan orang lain
    • Ini pendekatan yang sangat berpusat pada Linux
      Di kebanyakan sistem sudah ada Python bawaan, jadi itu cukup untuk skrip sederhana
      Kalau mengingat kita tetap harus memasang Go, menurut saya lebih masuk akal memakai Python dengan uv
      Seperti kata penulis bahwa ia “memulainya sedikit dengan trolling”, pada akhirnya ini memang soal preferensi terhadap Go
    • Menurut saya JS juga tidak buruk sebagai bahasa skrip
      Cukup node bla.js lalu selesai
    • Tipe selalu perlu diperhatikan
      Kita harus tahu fungsi mengembalikan apa, dan kalau sudah paham bahasanya, tipe dasar biasanya cukup diingat saja
      Ini juga berlaku pada bahasa bertipe statis
    • Python bagus untuk pengembang, tetapi untuk distribusi atau integrasi itu mimpi buruk
      Kalau mempertimbangkan orang lain, sebaiknya jangan menulis kode distribusi dengan Python
  • Saya tadinya mengira ini akan jadi kritik terhadap Python, tetapi ternyata justru tip yang berguna
    Kalau bahasanya memakai // sebagai komentar, trik ini bisa diterapkan juga
    C/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL, dan lainnya memungkinkan
    Terutama menarik untuk membuat demo grafis satu file dengan GLSL
    Contoh Shadertoy
    Di C, ini juga bisa dilakukan dengan komentar blok seperti /*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */

    • Swift punya proyek swift-sh yang bisa menjalankan skrip dengan dependensi eksternal
      Konsepnya seperti uv untuk Swift
      Swift juga secara resmi mendukung shebang
    • Di C/C++, #! juga bisa langsung dipakai
      Dulu pada masa TCC, orang memakai cara ini untuk “C scripting”
      Dalam proyek besar, skrip build akan membaca manifest lalu build dan menjalankannya
      Tetapi karena kontrol lingkungannya sulit, pendekatan ini kurang cocok untuk pekerjaan nyata
    • Rust tidak perlu trik semacam ini
      Rust mendukung shebang secara langsung
  • Kalau ingin bahasa yang lebih ergonomis, .NET 10 juga punya fitur “run file directly”
    Ia mendukung shebang, dan otomatis memasang paket dari dalam skrip
    Dengan direktif #:sdk, aplikasi web pun bisa langsung dijalankan

    • Saya juga baru hari ini menulis skrip C# dengan fitur itu, dan pengalamannya cukup bagus
      Hanya saja kompilasi AOT masih terasa kasar
  • Awalnya saya kira ini akan jadi kritik terhadap Python, tetapi malah membuat saya memikirkan arah ekosistem bahasa
    Menurut saya, fakta bahwa ML terikat pada Python adalah kesalahan besar
    Karena lambat, sistem tipenya tidak nyaman, dan distribusinya sulit
    Sekarang sudah saatnya mempertimbangkan alternatif seperti TypeScript, Go, dan Rust

    • Setuju
      Tetapi alasan ML memilih Python adalah karena FFI berbasis C
      NodeJS, Rust, dan Go lemah dalam hal FFI
      Di sinilah Python punya keunggulan
      Yang ideal adalah bahasa yang sesederhana Python tetapi punya sistem tipe dan skema distribusi yang lebih baik
    • Saya tidak setuju dengan gagasan menggantinya dengan TypeScript
      Saya tidak ingin mengganti Python dengan bahasa yang lahir dari ekosistem JS
    • ML pindah ke Python karena tekanan pasar
      Lisp atau Lua (Torch) sebenarnya lebih cocok, tetapi Python dipilih karena kesederhanaannya
      Saya sendiri sedang mengembangkan framework ML berbasis Lisp, tetapi sepertinya akan sulit diadopsi
    • Neraka dependensi Python masih sangat parah
      Karena masalah kompatibilitas versi, tidak adanya semver, dan ekosistem yang tidak stabil, rasanya Python justru tertinggal dari JS
      JS/Node sudah matang dalam 10 tahun terakhir, tetapi Python masih seperti tertahan di 2012
      Sangat disayangkan bahwa ML distandardkan ke Python
    • Saya menginginkan bahasa yang sederhana dan ekspresif, tetapi juga bertipe kuat + dikompilasi native
      Saat membuat alat CLI, Go jauh lebih cepat daripada Python
      Saya sempat kembali ke Python karena perbedaan LOC, tetapi setiap kali menjalankannya saya malah rindu Go
      Mungkin OCaml yang ideal, tetapi tooling yang kuno terasa membebani
  • Masalah dengan skrip Go adalah baris pertama tidak boleh memiliki spasi awal
    Karena gopls memaksa pemformatan otomatis
    Konsistensi format juga harus dijaga di CI, jadi ini penting dalam praktik
    Tetapi masalah yang lebih besar adalah tidak bisa memakai go.mod
    Artinya, kita tidak bisa menentukan versi dependensi sehingga jaminan kompatibilitas menjadi lebih lemah

    • Tetap saja, versi mayor terkunci lewat jalur import, jadi secara default masih kompatibel
    • Ini adalah masalah kompatibilitas pada tingkat bahasa/runtime, bukan masalah dependensi itu sendiri