Skrip shell tidak selalu dijalankan oleh program yang sama pada setiap sistem. Topik ini membedakan tiga tingkat bahasa shell, yaitu POSIX sh, bash, dan zsh, menjelaskan cara shebang memilih penafsir, serta menunjukkan bashism yang gagal di penafsir POSIX. Setelah membaca topik ini, pilihan shebang untuk sebuah skrip dapat ditentukan secara sadar menurut tingkat portabilitas yang dituju.
Tiga lapis bahasa shell#
POSIX (Portable Operating System Interface) adalah keluarga standar untuk sistem operasi bertipe Unix, dan salah satu bagiannya, POSIX.1-2017 Shell Command Language, menetapkan bahasa shell minimum yang wajib dipahami setiap shell standar. dash (Debian Almquist shell) adalah implementasi yang sengaja hanya menargetkan lapisan POSIX, sehingga ukurannya kecil dan eksekusinya cepat. bash (Bourne Again shell) menambahkan array, uji kondisi [[ ]], dan banyak fitur lain di atas lapisan POSIX. zsh (Z shell) juga menambahkan fitur, tetapi arah perluasannya berbeda, misalnya penomoran array yang dimulai dari 1.
Tabel berikut merangkum tingkat portabilitas beberapa fitur pada ketiga lapis tersebut.
| Perilaku | POSIX sh | bash | zsh |
|---|---|---|---|
Uji kondisi [ ] | tersedia | tersedia | tersedia |
Uji kondisi [[ ]] | tidak ada | tersedia | tersedia |
| array berindeks | tidak ada | tersedia, indeks mulai 0 | tersedia, indeks mulai 1 |
substitusi proses <( ) | tidak ada | tersedia | tersedia |
| pemisahan kata pada perluasan variabel tanpa kutip | dilakukan | dilakukan | tidak dilakukan secara default |
substitusi perintah $( ) | tersedia | tersedia | tersedia |
Baris pemisahan kata pada tabel menunjukkan bahwa zsh bukan pengganti bash yang sepenuhnya kompatibel, sehingga skrip yang diuji di bash tetap perlu diuji lagi sebelum dinyatakan kompatibel dengan zsh. Batasan serupa juga berlaku terhadap POSIX. Subset POSIX perlu dipisahkan secara eksplisit dari bashism sesuai profil POSIX.1-2017 agar batas portabilitas sebuah skrip dapat dinyatakan dengan pasti, bukan hanya diasumsikan.
Shebang menentukan penafsir#
Shebang adalah baris pertama skrip yang diawali #! dan menyebut program penafsir yang menjalankan skrip tersebut. Ketika skrip dieksekusi langsung sebagai ./skrip.sh, kernel (inti sistem operasi) membaca shebang dan menyerahkan isi skrip ke program yang disebut di baris itu. Shebang #!/bin/sh menyerahkan skrip ke program /bin/sh, sedangkan #!/usr/bin/env bash mencari program bash melalui PATH sehingga tetap bekerja pada sistem yang menaruh bash di lokasi tidak standar, misalnya FreeBSD atau instalasi Homebrew di macOS.
Program di balik /bin/sh berbeda antar sistem. Identifikasi berikut dilakukan di sandbox ini:
# mengidentifikasi program yang sesungguhnya berada di balik /bin/sh
readlink -f /bin/sh/usr/bin/dashPada sandbox ini /bin/sh menunjuk dash, sehingga skrip dengan shebang #!/bin/sh dijalankan dash dan hanya boleh memakai subset POSIX. Pada distribusi lain /bin/sh bisa menunjuk bash, dan pada macOS /bin/bash berupa bash versi 3.2 yang tidak memiliki banyak fitur bash modern. Keadaan ini membuat shebang menjadi keputusan portabilitas yang sesungguhnya, bukan formalitas penulisan.
Bashism dan pesan error di dash#
Bashism adalah fitur bash yang berada di luar subset POSIX, misalnya array dan uji kondisi [[ ]]. Skrip berikut memakai keduanya, sehingga skrip hanya berfungsi ketika dijalankan oleh bash.
#!/usr/bin/env bash
# contoh bashism: array tidak dikenal POSIX sh
warna=("merah" "hijau" "biru")
echo "warna pertama: ${warna[0]}"
echo "jumlah warna: ${#warna[@]}"warna pertama: merah
jumlah warna: 3Menjalankan skrip yang sama dengan dash menghasilkan error sintaks:
# menjalankan skrip bashism dengan penafsir POSIX
dash contoh-array.sh
echo "kode keluar: $?"contoh-array.sh: 3: Syntax error: "(" unexpected
kode keluar: 2Pesan error dash menunjuk baris 3, yaitu baris pertama yang memuat tanda kurung array, dan skrip berhenti dengan kode keluar 2 sebelum satu perintah pun dieksekusi. Perilaku serupa terlihat pada [[ ]]:
# [[ ]] tersedia di bash
[[ "abc" == a* ]] && echo "pola cocok"
# [[ ]] tidak dikenal dash
dash -c '[[ "abc" == a* ]] && echo "pola cocok"'
echo "kode keluar: $?"pola cocok
dash: 1: [[: not found
kode keluar: 127Kode keluar 127 berarti dash mencari perintah bernama [[ dan tidak menemukannya, karena dash tidak mengenal konstruksi tersebut sama sekali. Dua contoh di atas memperlihatkan pola kegagalan bashism yang khas, yaitu error muncul pada penafsir POSIX sementara bash menerima skrip tanpa keluhan.
Subset POSIX untuk skrip portabel#
Skrip portabel ditulis dengan membatasi konstruksi yang dipakai pada subset POSIX. Berkas log contoh dibuat terlebih dahulu:
# membuat berkas log contoh
printf '%s\n' "INFO suhu 24.5" "ERROR sensor 3" "INFO suhu 25.1" "ERROR sensor 4" > sensor.logSkrip berikut membuat laporan ringkas dari log tersebut dan hanya memakai konstruksi POSIX.
#!/bin/sh
# laporan ringkas log sensor (subset POSIX)
berkas="${1:-sensor.log}"
if [ ! -f "$berkas" ]; then
echo "berkas tidak ada: $berkas" >&2
exit 1
fi
jumlah=$(grep -c "ERROR" "$berkas")
echo "jumlah baris ERROR: $jumlah"jumlah baris ERROR: 2Menjalankan skrip yang sama dengan dua penafsir menghasilkan keluaran yang identik:
# skrip portabel dijalankan dengan dua penafsir
bash laporan-portabel.sh sensor.log
dash laporan-portabel.sh sensor.logjumlah baris ERROR: 2
jumlah baris ERROR: 2Konstruksi yang dipakai skrip di atas semuanya berada di lapis POSIX, yaitu uji kondisi [ ], substitusi perintah $( ), dan perluasan parameter ${1:-sensor.log} yang memberi nilai default pada argumen. Konstruksi yang perlu dihindari ketika targetnya penafsir POSIX antara lain array, [[ ]], substitusi proses <( ), dan opsi echo yang tidak standar seperti echo -e. Aturan praktisnya sederhana: tulis skrip dengan subset terkecil yang memenuhi kebutuhan, lalu verifikasi dengan menjalankannya pada penafsir POSIX sebelum dinyatakan portabel.
Kesalahan umum#
- Skrip diuji hanya dengan bash tetapi dipasang dengan shebang
#!/bin/sh. Penyebabnya adalah asumsi bahwa/bin/shselalu bash, sedangkan pada Debian dan Ubuntu program itu adalah dash. Perbaikannya adalah menguji dengandash skrip.shatau mengubah shebang menjadi#!/usr/bin/env bash. - Fitur bash dipakai padahal targetnya penafsir lain. Array pada zsh berindeks mulai 1, sedangkan pada bash mulai 0, sehingga skrip bash yang dipindahkan ke zsh dapat membaca elemen yang salah. Perbaikannya adalah menyatakan tingkat portabilitas skrip sejak awal dan menaatinya secara konsisten.
- Alias dipakai di dalam skrip. Shell non-interaktif tidak memperluas alias, sehingga skrip yang mengandalkan alias dari berkas konfigurasi gagal. Perbaikannya adalah menggunakan fungsi shell, yang tetap bekerja pada sesi non-interaktif.
Ringkasan#
- POSIX.1-2017 menetapkan lapis bahasa shell minimum, dash menargetkan lapis itu saja, sedangkan bash dan zsh menambahkan fitur di atasnya dengan arah yang berbeda.
- Shebang menentukan penafsir yang benar-benar menjalankan skrip, dan
/bin/shtidak selalu menunjuk bash. - Bashism seperti array dan
[[ ]]gagal di dash dengan pesanSyntax erroratau kode keluar 127. - Skrip portabel ditulis dalam subset POSIX dan diuji dengan
dashmaupunbashsebelum dipasang.
Referensi#
- POSIX.1-2017 Shell Command Language — The Open Group — Spesifikasi bahasa shell yang menjadi lapis portabilitas minimum.
- GNU Bash Reference Manual — Free Software Foundation — Dokumentasi fitur bash di atas subset POSIX.
- dash(1) — man7.org — Halaman manual dash, penafsir POSIX ringan pada Debian dan Ubuntu.