Apakah kurung kotak ganda [[]] lebih disukai daripada kurung kotak tunggal [] di Bash?

574

Seorang rekan kerja mengklaim baru-baru ini dalam ulasan kode bahwa [[ ]]konstruk lebih disukai daripada [ ]konstruk suka

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Dia tidak bisa memberikan alasan. Apakah ada satu?

Leonard
sumber
19
Bersikaplah fleksibel, terkadang biarkan diri Anda mendengarkan saran tanpa memerlukan penjelasan mendalamnya :) Mengenai [[kode itu bagus dan jelas, tetapi ingatlah hari itu ketika Anda akan mem-porting skrip Anda pada sistem dengan shell default yang bukan bashatau ksh, dll [lebih jelek, rumit, tetapi berfungsi seperti AK-47dalam situasi apa pun.
benteng
178
@rook Anda dapat mendengarkan saran tanpa penjelasan yang mendalam, tentu saja. Tetapi ketika Anda meminta penjelasan dan tidak mendapatkannya, biasanya ada tanda merah. "Percaya, tapi verifikasi" dan semua itu.
Josip Rodin
6
Lihat juga: Apa perbedaan antara operator Bash [[vs [vs (vs (( pada Unix & Linux SE.
codeforester
1
@rook dengan kata lain "lakukan apa yang diperintahkan dan jangan ajukan pertanyaan"
glyph
@ Rook Itu tidak masuk akal.
Rakib

Jawaban:

608

[[memiliki lebih sedikit kejutan dan umumnya lebih aman untuk digunakan. Tapi itu tidak portabel - POSIX tidak menentukan apa yang dilakukannya dan hanya beberapa shell yang mendukungnya (selain bash, saya mendengar ksh juga mendukungnya). Misalnya, Anda bisa melakukannya

[[ -e $b ]]

untuk menguji apakah ada file. Tetapi dengan [, Anda harus mengutip $b, karena itu memecah argumen dan memperluas hal-hal seperti "a*"(di mana [[menerimanya secara harfiah). Itu juga ada hubungannya dengan bagaimana [bisa menjadi program eksternal dan menerima argumennya seperti biasanya seperti setiap program lainnya (meskipun itu juga bisa menjadi builtin, tetapi kemudian masih belum penanganan khusus ini).

[[juga memiliki beberapa fitur bagus lainnya, seperti pencocokan ekspresi reguler =~dan operator seperti mereka dikenal dalam bahasa C-like. Berikut ini adalah halaman yang bagus tentang hal itu: Apa perbedaan antara tes, [dan [[? dan Tes Bash

Johannes Schaub - litb
sumber
21
Mempertimbangkan bahwa bash ada dimana-mana akhir-akhir ini, saya cenderung berpikir itu sangat portabel. Satu-satunya pengecualian umum bagi saya adalah pada platform busybox - tetapi Anda mungkin ingin melakukan upaya khusus untuk itu, mengingat perangkat keras yang dijalankannya.
senjata
14
@guns: Memang. Saya menyarankan agar kalimat kedua Anda membantah yang pertama; jika Anda menganggap pengembangan perangkat lunak secara keseluruhan, "bash ada di mana-mana" dan "pengecualiannya adalah platform busybox" sama sekali tidak kompatibel. busybox tersebar luas untuk pengembangan yang disematkan.
Lightness Races dalam Orbit
11
@guns: Walaupun bash diinstal pada kotak saya, mungkin bash tidak mengeksekusi skrip (saya mungkin memiliki / bin / sh yang terhubung ke beberapa varian dash, seperti standar pada FreeBSD dan Ubuntu). Ada keanehan tambahan dengan Busybox, juga: dengan opsi kompilasi standar saat ini, ia mem-parsing [[ ]]tetapi mengartikannya sebagai sama artinya [ ].
dubiousjim
59
@dubiousjim: Jika Anda menggunakan konstruksi bash-only (seperti ini), Anda seharusnya memiliki #! / bin / bash, bukan #! / bin / sh.
Nick Matteo
16
Itulah mengapa saya membuat skrip saya digunakan #!/bin/shtetapi kemudian beralih untuk digunakan #!/bin/bashsegera setelah saya mengandalkan beberapa fitur BASH spesifik, untuk menunjukkan itu bukan lagi Bourne shell portable.
anthony
151

Perbedaan perilaku

Beberapa perbedaan pada Bash 4.3.11:

  • Ekstensi POSIX vs Bash:

  • perintah reguler vs sihir

    • [ hanyalah perintah biasa dengan nama yang aneh.

      ]hanyalah argumen [yang mencegah argumen lebih lanjut digunakan.

      Ubuntu 16.04 sebenarnya memiliki executable yang /usr/bin/[disediakan oleh coreutils , tetapi versi built-in bash lebih diutamakan.

      Tidak ada yang diubah dalam cara Bash mem-parsing perintah.

      Secara khusus, <adalah pengalihan, &&dan ||menggabungkan beberapa perintah, ( )menghasilkan subkulit kecuali lolos \, dan perluasan kata terjadi seperti biasa.

    • [[ X ]]adalah konstruksi tunggal yang membuat Xdiurai secara ajaib. <, &&, ||Dan ()diperlakukan khusus, dan aturan kata membelah berbeda.

      Ada juga perbedaan lebih lanjut seperti =dan =~.

      Di Bashese: [adalah perintah bawaan , dan [[merupakan kata kunci: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && dan ||

    • [[ a = a && b = b ]]: benar, logis dan
    • [ a = a && b = b ]: kesalahan sintaks, &&diurai sebagai pemisah perintah ANDcmd1 && cmd2
    • [ a = a -a b = b ]: setara, tetapi ditinggalkan oleh POSIX³
    • [ a = a ] && [ b = b ]: POSIX dan setara yang dapat diandalkan
  • (

    • [[ (a = a || a = b) && a = b ]]: Salah
    • [ ( a = a ) ]: kesalahan sintaks, ()ditafsirkan sebagai subkulit
    • [ \( a = a -o a = b \) -a a = b ]: ekuivalen, tetapi ()ditinggalkan oleh POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] Setara dengan POSIX⁵
  • pemisahan kata dan pembuatan nama file berdasarkan ekspansi (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: true, kutipan tidak diperlukan
    • x='a b'; [ $x = 'a b' ]: kesalahan sintaks, diperluas ke [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: kesalahan sintaks jika ada lebih dari satu file di direktori saat ini.
    • x='a b'; [ "$x" = 'a b' ]: Setara POSIX
  • =

    • [[ ab = a? ]]: benar, karena tidak cocok dengan pola ( * ? [adalah sihir). Tidak glob berkembang ke file dalam direktori saat ini.
    • [ ab = a? ]: a?glob berkembang. Jadi mungkin benar atau salah tergantung pada file di direktori saat ini.
    • [ ab = a\? ]: false, bukan ekspansi glob
    • =dan ==sama di keduanya [dan [[, tetapi ==merupakan ekstensi Bash.
    • case ab in (a?) echo match; esac: Setara POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, kehilangan sihir dengan ''
    • [[ ab? =~ 'ab?' ]]: benar
  • =~

    • [[ ab =~ ab? ]]: true, POSIX memperpanjang kecocokan ekspresi reguler , ?tidak glob berkembang
    • [ a =~ a ]: kesalahan sintaks. Tidak ada yang setara dengan bash.
    • printf 'ab\n' | grep -Eq 'ab?': Setara POSIX (hanya data garis tunggal)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Setara POSIX.

Rekomendasi : selalu gunakan [].

Ada setara POSIX untuk setiap [[ ]]konstruk yang pernah saya lihat.

Jika Anda menggunakan [[ ]]Anda:

  • kehilangan portabilitas
  • memaksa pembaca untuk mempelajari seluk-beluk ekstensi bash yang lain. [hanyalah perintah biasa dengan nama yang aneh, tidak ada semantik khusus yang terlibat.

¹ Terinspirasi dari [[...]]konstruksi yang setara di shell Korn

² tetapi gagal untuk beberapa nilai aatau b(suka +atau index) dan melakukan perbandingan numerik jika adan bterlihat seperti bilangan bulat desimal. expr "x$a" '<' "x$b"bekerja di sekitar keduanya.

³ dan juga gagal untuk beberapa nilai aatau bsuka !atau (.

⁴ di bash 3.2 dan di atas dan kompatibilitas yang disediakan untuk bash 3.1 tidak diaktifkan (seperti dengan BASH_COMPAT=3.1)

⁵ meskipun pengelompokan (di sini dengan {...;}grup perintah alih-alih (...)yang akan menjalankan subkulit yang tidak perlu) tidak diperlukan karena operator ||dan &&shell (sebagai lawan dari ||dan && [[...]]operator atau -o/ -a [operator) memiliki prioritas yang sama. Jadi [ a = a ] || [ a = b ] && [ a = b ]akan setara.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
4
Penjelasan yang bagus. Saya menggunakan [untuk alasan yang sama.
Gordon
2
Di Bashese :) ha. Klarifikasi yang bagus tentang perbedaan dan alasan untuk tetap menggunakan POSIX.
Jonathan Komar
56

[[ ]]memiliki lebih banyak fitur - saya sarankan Anda melihat pada Panduan Bash Scripting Lanjutan untuk info lebih lanjut, khususnya bagian perintah uji diperpanjang di Bab 7. Pengujian .

Kebetulan, seperti catatan panduan, [[ ]]diperkenalkan di ksh88 (versi 1988 dari shell Korn).

Nils von Barth
sumber
5
Ini jauh dari bashism, ini pertama kali diperkenalkan di shell Korn.
Henk Langeveld
6
@ Thomas, ABS sebenarnya dianggap sebagai referensi yang sangat buruk di banyak kalangan; sementara ia memiliki banyak informasi yang akurat, ia cenderung sangat berhati-hati untuk menghindari menampilkan praktik-praktik buruk dalam contoh-contohnya, dan menghabiskan sebagian besar hidupnya tanpa dipelihara.
Charles Duffy
@CharlesDuffy terima kasih atas komentar Anda, dapatkah Anda menyebutkan alternatif yang baik. Saya bukan ahli dalam skrip shell, saya mencari panduan yang bisa saya konsultasikan untuk menulis skrip setiap setengah tahun sekali.
Thomas
9
@ Thomas, BashFAQ Wooledge dan wiki terkait adalah apa yang saya gunakan; mereka secara aktif dikelola oleh penghuni saluran Freenode #bash (yang, meskipun terkadang berduri, cenderung sangat peduli tentang kebenaran). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , adalah halaman yang secara langsung relevan dengan pertanyaan ini.
Charles Duffy
13

Dari pembanding, tes, braket, atau braket ganda mana yang tercepat? ( http://bashcurescancer.com )

Braket ganda adalah "perintah majemuk" di mana sebagai tes dan braket tunggal adalah built-in shell (dan pada kenyataannya adalah perintah yang sama). Dengan demikian, braket tunggal dan braket ganda mengeksekusi kode yang berbeda.

Tes dan braket tunggal adalah yang paling portabel karena ada sebagai perintah terpisah dan eksternal. Namun, jika Anda menggunakan BASH versi modern apa pun, braket ganda didukung.

f3lix
sumber
7
Ada apa dengan obsesi skrip shell tercepat ? Saya ingin yang paling portabel dan tidak peduli perbaikan [[mungkin membawa. Tapi kemudian, aku kentut sekolah tua :-)
Jens
1
Saya pikir banyak shell membuktikan versi builtin [dan testmeskipun versi eksternal juga ada.
dubiousjim
4
@Jens secara umum saya setuju: seluruh tujuan skrip adalah (apakah?) Portabilitas (jika tidak, kami akan kode & kompilasi, bukan skrip) ... dua pengecualian yang dapat saya pikirkan adalah: (1) penyelesaian tab (di mana skrip penyelesaian bisa sangat panjang dengan banyak logika kondisional); dan (2) permintaan super ( PS1=...crazy stuff...dan / atau $PROMPT_COMMAND); untuk ini, saya tidak ingin ada keterlambatan yang jelas dalam pelaksanaan skrip.
michael
1
Beberapa obsesi tercepat adalah gaya. Semuanya sama, mengapa tidak memasukkan konstruk kode yang lebih efisien ke gaya default Anda, terutama jika konstruk itu juga menawarkan lebih banyak keterbacaan? Sejauh portabilitas, banyak tugas yang cocok untuk bash secara inheren non-portabel. Misalnya, saya harus menjalankan apt-get updatejika sudah lebih dari X jam sejak terakhir kali dijalankan. Ini sangat melegakan ketika seseorang dapat meninggalkan portabilitas dari daftar kendala yang sudah terlalu lama untuk kode.
Ron Burk
5

Jika Anda mengikuti panduan gaya Google :

Tes, [dan[[

[[ ... ]]mengurangi kesalahan karena tidak ada perluasan nama jalur atau pemisahan kata yang terjadi antara [[dan ]], dan [[ ... ]]memungkinkan untuk pencocokan ekspresi reguler di mana [ ... ]tidak.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
sumber
1
Google menulis " tidak ada perluasan pathname ... terjadi " namun [[ -d ~ ]]mengembalikan true (yang menyiratkan ~diperluas ke /home/user). Saya pikir Google seharusnya lebih tepat dalam menulisnya.
JamesThomasMoon1979
2

Situasi khas di mana Anda tidak dapat menggunakan [[dalam skrip configure.ac autotools, ada tanda kurung memiliki arti khusus dan berbeda, sehingga Anda harus menggunakan testbukan [atau [[- Perhatikan tes itu dan [merupakan program yang sama.

Vicente Bolea
sumber
2
Mengingat autotool bukan shell POSIX, mengapa Anda berharap [akan didefinisikan sebagai fungsi shell POSIX?
Gordon
Karena skrip autoconf terlihat seperti skrip shell, dan skrip shell menghasilkan, dan sebagian besar perintah shell beroperasi di dalamnya.
vy32
-1

[[]] kurung ganda tidak didukung di bawah versi SunOS tertentu dan deklarasi fungsi dalam yang sepenuhnya tidak didukung oleh: GNU bash, versi 2.02.0 (1) -release (sparc-sun-solaris2.6)

pemulung
sumber
1
sangat benar, dan sama sekali tidak ngawur. portabilitas bash di versi yang lebih lama harus dipertimbangkan. Orang mengatakan "bash ada di mana-mana dan portabel, kecuali mungkin (masukkan OS esoterik di sini) " - tetapi dalam pengalaman saya, solaris adalah salah satu platform di mana perhatian khusus harus diberikan pada portabilitas: tidak hanya mempertimbangkan versi bash yang lebih lama pada sebuah OS yang lebih baru, masalah / bug dengan array, fungsi, dll; tetapi bahkan utilitas (digunakan dalam skrip) seperti tr, sed, awk, tar memiliki keanehan & kekhasan pada solaris yang harus Anda selesaikan.
michael
2
Anda benar sekali ... begitu banyak utilitas non POSIX di Solaris, lihat saja "df" output dan argumen ... Shame on Sun. Semoga itu menghilang sedikit demi sedikit (kecuali di Kanada).
pemulung
7
Solaris 2.6 serius? Itu dirilis pada tahun 1997 dan mengakhiri dukungan pada tahun 2006. Saya kira jika Anda masih menggunakan itu maka Anda memiliki masalah lain !. Kebetulan itu menggunakan Bash v2.02 yang merupakan yang memperkenalkan kurung ganda sehingga harus bekerja bahkan pada sesuatu yang setua itu. Solaris 10 dari 2005 menggunakan Bash 3.2.51 dan Solaris 11 dari 2011 menggunakan Bash 4.1.11.
peterh
1
Portabilitas Script Ulang. Pada sistem Linux umumnya hanya ada edisi dari masing-masing alat dan itu adalah edisi GNU. Pada Solaris, Anda biasanya memiliki pilihan antara edisi asli Solaris atau edisi GNU (misalnya Solaris tar vs GNU tar). Jika Anda bergantung pada ekstensi spesifik GNU, maka Anda harus menyatakannya dalam skrip agar portabel. Pada Solaris Anda melakukannya dengan awalan dengan "g", misalnya `ggrep` jika Anda ingin grep GNU.
peterh
-2

Singkatnya, [[lebih baik karena tidak perlu proses lain. Tidak ada tanda kurung atau braket tunggal yang lebih lambat dari braket ganda karena braket proses lain.

unix4linux
sumber
8
Test dan [adalah nama untuk perintah builtin yang sama di bash. Coba gunakan type [untuk melihat ini.
AB
1
@alberge, itu benar, tetapi [[, berbeda dari [, sintaksinya ditafsirkan oleh penerjemah baris perintah bash. Di bash, coba ketikkan type [[. unix4linux benar bahwa meskipun [tes Bourne-shell klasik memotong proses baru untuk menentukan nilai kebenaran, [[sintaks (dipinjam dari ksh oleh bash, zsh, dll) tidak.
Tim Gilbert
2
@Tim, saya tidak yakin shell Bourne mana yang Anda bicarakan, tetapi [built-in untuk Bash serta Dash ( /bin/shdalam semua distribusi Linux yang diturunkan dari Debian).
AB
1
Oh, aku mengerti maksudmu, itu benar. Saya sedang memikirkan sesuatu seperti, katakanlah, / bin / sh pada sistem Solaris atau HP / UX yang lebih lama, tetapi tentu saja jika Anda harus kompatibel dengan yang tidak akan Anda gunakan [[juga.
Tim Gilbert
1
@alberge Shell Bourne bukan Bash (alias Bourne Again SHell ).
kiamlaluno