- Pada sistem mirip Unix, bisa ada berkas eksekusi
/bin/[yang namanya berupa satu simbol; sintaks yang tampak seperti ekspresi kondisi shell sebenarnya berdiri di atas eksekusi perintah dan kode keluar testmengevaluasi ekspresi dan mengembalikan 0 jika benar, 1 jika salah; jika dipanggil sebagai[, ia juga memeriksa apakah argumen terakhir adalah]- Banyak shell juga menyediakan
testdan[sebagai perintah bawaan, sehingga pesan galat atau perilaku implementasi bawaan shell bisa berbeda dari/bin/testeksternal - Ekstensi Bash
[[bukan perintah eksternal melainkan sintaks bawaan, sehingga menerapkan aturan yang berbeda dari[, misalnya membandingkanlong*sebagai string literal tanpa ekspansi glob seperti pada contoh - Untuk skrip portabel,
[adalah pilihan yang tepat; jika khusus Bash, lebih baik memakai[[secara konsisten, tetapi pilihlah dengan memahami perbedaan aturan ekspansi di antara keduanya
Hakikat /bin/[ dan /bin/test
- Pada sistem Unix, bisa ada berkas eksekusi
/bin/[yang namanya berupa satu simbol- Contoh perintah
ls /bin/?menampilkan/bin/[
- Contoh perintah
/bin/[dan/bin/testbisa menunjuk ke biner yang sama- Dalam contoh, kedua jalur muncul sebagai berkas dengan inode dan ukuran yang sama
- Namun, tidak harus berupa hard link di semua sistem
testadalah program yang mengevaluasi ekspresi di shell- Perbandingan string
- Perbandingan angka
- Pemeriksaan kondisi berkas
- Jika hasil evaluasi benar, ia mengembalikan kode keluar 0; jika salah, 1
Mengapa [ berperilaku seperti perintah
test a = bsulit terlihat seperti ekspresi kondisi, tetapi jika logika yang sama ditulis sebagai[ a = b ], bentuknya menjadi lebih familier[tampak seperti sintaks tersendiri, tetapi sebenarnya adalah pemanggilan perintahif [ a = b ]; then ... fimenjalankan perintah[dan memeriksa kode keluarnya- Jika dipanggil sebagai
[, program memeriksa apakah argumen terakhir adalah kurung siku penutup]
- Pernyataan
iftidak menafsirkan ekspresi kondisi secara langsung, melainkan bercabang berdasarkan kode keluar perintah yang diberikantest a = a; echo $?adalah0test a = b; echo $?adalah1[ a = a ]; echo $?adalah0[ a = b ]; echo $?adalah1
- Dalam konteks yang sama,
truedanfalsejuga dapat dipandang sebagai biner pembantu yang mengembalikan kode keluar
Perbedaan antara biner eksternal dan perintah bawaan shell
- Karena
testdan[sering digunakan dalam skrip shell, kebanyakan shell juga mengimplementasikannya sebagai perintah bawaan - Untuk masukan yang sama, keluaran biner eksternal dan perintah bawaan shell bisa berbeda
/bin/test a bmenghasilkantest: a: unexpected operatortest a bmenghasilkandash: 2: test: a: unexpected operator
- Perbedaan semacam ini bisa terjadi bukan hanya pada
testdan[, tetapi juga pada perintah yang tampak sederhana sepertiecho - Implementasi bawaan berbeda-beda antar-shell, sehingga perilaku skrip dapat berubah tergantung shell yang menjalankannya
Aturan tersendiri yang diterapkan ekstensi Bash [[
[[adalah ekstensi Bash dan dapat menggantikan penggunaan[- Perbedaan terbesar adalah
[[selalu merupakan sintaks bawaan- Berbeda dari
[yang bisa dijalankan sebagai biner eksternal, pada[[Bash dapat mengubah aturan bahasa di dalam ekspresi
- Berbeda dari
- Dalam contoh glob,
[dan[[berperilaku berbeda- Setelah
touch long-name,[ long* = long-name ] && echo matchmencetakmatch - Pada argumen untuk perintah
[, aturan ekspansi shell biasa diterapkan, sehinggalong*diekspansi menjadilong-namedi direktori [[ long* = long-name ]] && echo matchtidak mencetak apa pun[[memperlakukanlong*sebagai string literal dan membandingkannya apa adanya denganlong-name, sehingga gagal
- Setelah
- Jika skrip khusus Bash, Anda juga dapat menggunakan fitur seperti pencocokan regex
=~dengan[[
Apa yang sebaiknya dipilih dalam skrip
- Untuk skrip shell yang portabel, lebih tepat menggunakan
[ testjuga dapat digunakan, tetapi bukan pilihan yang umum- Jika skrip khusus Bash, lebih baik menggunakan
[[secara konsisten - Shell itu sendiri juga memiliki operator ekspresi seperti
!,&&, dan||- Operator ini bekerja berdasarkan status keluar perintah
grep ^hello$ ... && grep ^bye$ ...membuat kode keluar keseluruhan menjadi0jika kedua perintah berhasil- Jika
greppertama gagal, perintah setelah&&tidak ikut berhasil, sehingga kode keluar keseluruhan menjadi1
- Karena itu, ekspresi
test/[dan operator logika shell dapat dikombinasikan dalam satu pernyataan kondisi- Contoh:
[ a = b ] || grep -q ^hello$ /usr/share/dict/words
- Contoh:
- POSIX tidak mensyaratkan
/bin/[dan/bin/testharus berupa hard link- Di NetBSD, keduanya berupa hard link
- macOS Catalina menyediakan salinan terpisah dari biner yang sama
- Debian testing menyediakan dua biner yang berbeda
- Spesifikasi POSIX tidak mensyaratkan kedua berkas tersebut berupa tautan
1 komentar
Pendapat Hacker News
Saya penulis aslinya. Terima kasih sudah membagikannya, dan saya senang ini sampai naik ke front page. Judulnya mungkin memang sebaiknya diberi (2020), dan karena "test" sebenarnya merujuk ke sebuah command, sebaiknya tidak ditulis dengan huruf kapital
Saya juga menulis artikel terkait pada 2021; artikel itu membahas sampai operator
[[di bash, jadi sepertinya menarik untuk dibaca dalam konteks ini: https://jmmv.dev/2021/08/useless-use-of-gnu.html[[bukan built-in command, melainkan pada dasarnya lebih dekat ke elemen sintaks. Mungkin secara internal ia memakai built-in command yang hampir tidak bisa diakses, tetapi hal menariknya adalah]]juga merupakan reserved word meski tidak bisa muncul di posisi tempat reserved word bermaknaDi beberapa shell non-bash, keyword
functiondiperlukan untuk mendeklarasikan jenis fungsi tertentu.$(shell)dimakebisa membuat perbedaan performa yang terukur saat membangun banyak target. Namun, jika tidak melakukan apa-apa, itu tetap merugikan, jadi biasanya memang lebih tepat memakaiincludeuntuk memicu regenerasi. GNU sepenuhnya wajar mengabaikan POSIX, karena POSIX tidak terlalu berguna untuk menyelesaikan sebagian besar masalah nyata.secara eksplisit juga berguna, dan menambahkan opsi di belakang command yang baru saja diketik benar-benar praktis. Saya selalu kesal melihat command yang tidak mendukung hal-hal seperti ituDalam skrip, umumnya masuk akal untuk menyesuaikan diri dengan POSIX
sh. Setidaknya kita harus tahu apakah sedang memakai sintaks khusus Bash--ignore-casedanset -o pipefailmenurunkan portabilitas skrip. Itu sendiri benarNamun, artikel itu tidak menjelaskan mengapa pengguna Linux harus begitu peduli pada portabilitas. OpenBSD dan FreeBSD masih berjalan baik, tetapi jumlah penggunanya terlalu kecil sehingga rasanya bukan hal yang perlu terlalu dikhawatirkan. Demi fairness, bisa saja dikatakan bahwa sistem operasi seperti itu juga harus dipertimbangkan, tetapi di mana batasnya? Apakah hal obscure seperti
vxWorksjuga harus dipertimbangkan? Sisi BusyBox dan Alpine lebih menarik, tetapi perubahannya begitu besar sehingga hampir selalu tetap membutuhkan porting terpisah. Apakah ada alasan meyakinkan lain untuk peduli pada ekosistem non-GNU?if [ a = b ] || grep -q ^hello$ /usr/share/dict/words; then echo "test failed and grep succeeded"; fihanyalah cara memakai shell yang biasa dan sehari-hari?make $(shell …) expansion, padahal seharusnyamake $(shell ...) expansionSeperti yang tertulis dengan benar di isi artikel, itu tiga titik, bukan satu karakter elipsis, jadi
mldrsendiri juga tidak tepat. Mungkin dua bug yang tidak saling terkait kebetulan sama-sama berpengaruhKalau poin terakhir didorong satu langkah lagi, blok
ifitu sendiri juga bisa dihilangkan.if [ a = b ]; then echo "Oops!"; else echo "Expected; phew!"; fimenjadi[ a = b ] && echo "Oops!" || echo "Expected; phew!"Saya tidak tahu seberapa sering kita perlu melakukan ini, tetapi kadang berguna saat mencetak output debug secara kondisional ke standard error, seperti
[ "$debug" ] && echo "what's going on" >&2. Karena blokifsebenarnya memeriksa command biasa, hal sepertiif grep -q 'debug' /var/log/nginx/access.log; then echo "Debug request found!"; fijuga memungkinkan. Yang belum saya cari tahu adalah apakah harus menulis[ $(expr 1 + 1) -eq 2 ] && [ $(expr 2 + 2) -eq 3 ], atau memakai logical AND bawaantest, yaitu[ $(expr 1 + 1) -eq 2 -a $(expr 2 + 2) -eq 4 ]. Kalau performa bukan masalah, keduanya terlihat masuk akal karena alasan yang mirip[ a = b ] && echo "Oops!" || echo "Expected; phew!"tidak boleh diterima sebagai aturan umum. Kemungkinan bash akan menafsirkan baris itu seperti([ a = b ] && echo "Oops!") || echo "Expected; phew!"Jadi jika rangkaian command setelah
&&gagal, kode setelah||tetap akan dijalankan. Misalnya, jika>/dev/full echo "strings match"gagal karena error tulis, maka"strings don't match"akan tercetak meskipun string-nya sama. Ini berbeda dari makna blokifset -eyang memang sebaiknya dipakai,if [ a = b ]; then echo "Oops!"; fiakan berperilaku seperti yang diharapkan, tetapi[ a = b ] && echo "Oops!"akan keluar sebagai error ketika ekspresiatidak sama denganb-a,-oserta operator(,)ditandai sebagai akan dihentikan penggunaannya. Untuk detailnya, lihat "Application Usage" di https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t...-amaupun dua test dengan&&, kalau bisa memakai evaluasi aritmetika bash, tidak perlu menjalankanexpr:[ $((1+1)) -eq 2 ]Sejak beberapa tahun lalu saya mulai tidak memakai
[.testmenegaskan bahwa ini bukan sintaks, melainkan hanya sebuah command seperti command lainnya. Danman testjauh lebih nyaman daripada mengubek-ubekman bashman [selainman testDi Bash ada
help testsebagai cheatsheet cepat. Command[sudah sangat tua, dan sudah ada di Version 7 Unix pada 1979Setuju.
[dan[[yang khusus bash memang menimbulkan banyak kebingungan tentang apa yang sebenarnya terjadi, jadi sulit untuk merasa yakinNamun fakta bahwa
[[dijamin sebagai perintah bawaan jelas punya tujuan pada masa ketika performa skrip shell masih berarti, dan itu juga belum terlalu lamatestsaja. Saya tidak sering menulis skrip bash, jadi selalu tersandung pada pernyataanif, terutama aturan spasi. Setelah melihat alasannya, semuanya jadi terasa sangat masuk akal, dan memakaitestlebih jelas menunjukkan bahwa kita hanya meneruskan argumenJebakan terbesar pada
[dantestadalah perilaku dengan satu argumen. Misalnya, untuk memeriksa bahwa sebuah variabel tidak kosong, kita bisa menulis[ -n $FOO ]Namun jika
FOOtidak disetel, itu akan diekspansi bukan menjadi string kosong, melainkan tidak ada apa-apa, sehingga menjadi sama seperti[ -n ]. POSIX mensyaratkan bahwa dalam bentuk[dengan satu argumen, perintah harus berhasil jika argumen itu, dalam hal ini"-n", tidak kosong. Jadi ia keliru melaporkan bahwa$FOOtidak kosong. Variabel wajib diapit tanda kutiptestitu sendiri, melainkan pada shell itu sendiriPerilaku yang disebutkan itu masuk akal.
[ "$FOO" ]adalah bentuk yang selalu memeriksa apakah isinya tidak kosong, apa pun isinya, bahkan jika itu"-n"$FOOberisi spasi, ia akan diekspansi menjadi beberapa argumen. Pokoknya selalu apit variabel dengan tanda kutip[ x"$FOO" != x"" ][ -n "${FOO?}" ]agar skrip langsung berhenti ketika$FOOnull atau tidak disetelchubot menulis beberapa dokumen menarik yang menggali bagian-bagian lebih subtil dari
test/[/[[. Tulisan-tulisan lain di blog itu juga menjelaskan keanehan shell dengan cukup menarik¹ https://www.oilshell.org/blog/2017/08/31.html
² https://www.oilshell.org/blog/2016/11/18.html
Saya sama sekali tidak tahu bahwa
[adalah sebuah program, dan agak lucu bahwa ia memeriksa apakah argumen terakhir adalah kurung siku penutupTapi itu menjelaskan kenapa spasi diperlukan di kedua sisi kurung siku
[[khusus bash. Kalau Anda tahu hanya akan memakai bash, silakan gunakan. Tulisan itu membahas detailnya dengan baikhttps://zsh.sourceforge.io/Doc/Release/Conditional-Expressio...
[[sajazsh dan ksh juga punya, dan saya hampir yakin sebenarnya itu bermula dari ksh pada 1988 atau sebelumnya
testdan[ditentukan dalam POSIX dan biasanya memang ada sebagai binary sungguhan. Namun perintah bawaan shell bisa saja menutupinyaSebaliknya,
[[tidak ditentukan dalam POSIX, dan biasanya hanya ada sebagai bawaan shellMenargetkan shell penyebut bersama terkecil terasa seperti kekhawatiran kuno sepenuhnya
Saya tidak begitu paham kenapa pernyataan
ifterakhir membingungkan. Kalau alasannya karena saat pertama belajar skrip shell orang biasanya menganggap[adalah bagian dari bahasa skrip bash, bukan sekadar program lain, sekarang saya paham. Kalau bukan itu, saya ingin ada penjelasan kenapa itu mengejutkan[adalah binary, saya tetap tidak begitu mengerti kenapa itu membingungkan. Itu terlihat seperti bash yang sangat biasaSaya punya selera yang kuat soal shell, dan itu tidak terlalu cocok dengan kebanyakan dunia
Menurut saya
[tidak boleh dipakai sama sekali, dan hanyatestyang seharusnya dipakai.[membuat orang salah mengira mekanismenya adalah bagian dari sintaks bahasa, padahal sebenarnya itu hanya satu lagi "program". Di sini "program" mencakup perintah bawaan dan fungsi.if/||/&&melihat status keluar, dan setelah diekspansi, sebuah program tidak bisa melihat status keluar apa pun selain melihat variabel ajaib$?yang hanyalah string.casemelihat string, tetapi tidak bekerja berdasarkan status keluar dan juga tidak menetapkan status keluar sebagai bagian dari perilakucase ... esac. "Program"-lah yang menetapkan status keluar. Selain itu,[/testseharusnya hanya dipakai untuk mengevaluasi struktur filesystem, sepertitest -f /dev/null. Menurut saya evaluasi string seharusnya memakaicase. Tentu saja kebanyakan skrip membuat saya gatal, dan skrip yang saya tulis dianggap aneh oleh orang lainSaat memakai shell, saya lebih suka menaruh program setelah
ifdi baris terpisah lalu lanjut kethen. Ini untuk menekankan bahwa yang dilihat adalah "status keluar dari perintah terakhir sebelum then". Misalnya dalam struktur sepertiif; ls /tmp/goober; test -d /tmp/goober; echo "I'll always execute the then clause because echo will always return a 0 return code"; then ... fi,ifpada akhirnya hanya melihat status keluar dariecho, sehingga klausa then selalu dijalankantestmenjadi bukti bahwa saya setuju. Ini jalan yang sepi... Saya menyalahkan Google shell style guideKebiasaan memilih
testmuncul saat menulis skrip yang harus berjalan dishmaupunbash, tetapi saya terus memakainya karena secara semantik lebih masuk akal daripada memperlakukan karakter[seperti perintah. Fakta bahwa]bukan binary terpisah melainkan argumen untuk[juga aneh. Saya paham alasan teknisnya, tapi tetap terasa seperti hacktest. Puluhan!Saya diajari untuk memakai
[[hanya ketika ingin melakukan pencocokan regex. Contoh:if [[ "${foo}" =~ ^bar$ ]]; then echo Yes; fiSelain itu saya cukup memakai
"test"atau"[". Saat ini sudah 85.000 baris bash. Bukan berarti bash itu hebat, tetapi sejauh ini masih memenuhi kebutuhan saya untuk banyak halexprbisa mencocokkan regular expression dasar, dan juga bisa mengembalikan capture group1: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e...
[ "$foo" = bar ] && echo YesUntuk pencocokan substring,
[dan glob*biasanya sudah cukup. Misalnya seperti[ "$bar" = extra* ] && echo '$bar began with extra'. Dialek regex Bash itu primitif, jadi hampir tidak sepadan untuk dipakai dengan susah payah. Untuk hal yang rumit, sebaiknya gunakan alat lain sepertigrep,awk, atauperl. Kalau terobsesi mengerjakan semuanya dengan bash sampai tugas kompleks yang membutuhkan reusabilitas, modularitas, dan tipe bawaan yang lebih tinggi, hasilnya akan cepat sekali makin tidak sepadan.