Apa perbedaan antara operator Bash [[vs [vs (vs ((??)

247

Saya agak bingung tentang apa yang dilakukan operator ini secara berbeda ketika digunakan dalam bash (kurung, kurung ganda, kurung dan kurung ganda).

[[ , [ , ( , ((

Saya telah melihat orang menggunakannya jika pernyataan seperti ini:

if [[condition]]

if [condition]

if ((condition))

if (condition)
RetroCode
sumber
4
Anda mungkin ingin melihat unix.stackexchange.com/questions/tagged/test first
cuonglm
3
@cuonglm Ironic karena tautan itu menghasilkan pertanyaan ini sebagai hasil pertama. Paradoks!
Gila
5
Saya kira membaca dokumentasi bukanlah suatu pilihan?
Lightness Races dalam Orbit
34
Parenthesis dan kurung tidak mudah untuk dicari dalam dokumentasi, dan hanya itu yang Anda miliki jika Anda tidak tahu nama-nama fitur tersebut.
ilkkachu

Jawaban:

259

Sebuah ifpernyataan biasanya terlihat seperti

if commands1
then
   commands2
else
   commands3
fi

The thenklausul dijalankan jika kode keluar dari commands1nol. Jika kode keluar bukan nol, maka elseklausa dieksekusi. commands1bisa sederhana atau kompleks. Hal ini dapat, misalnya, menjadi urutan satu atau lebih jaringan pipa dipisahkan oleh salah satu operator ;, &, &&, atau ||. The ifkondisi di bawah ini merupakan kasus hanya khusus commands1:

  1. if [ condition ]

    Ini adalah testperintah shell tradisional . Ini tersedia di semua shell POSIX. Perintah tes menetapkan kode keluar dan ifpernyataan itu bertindak sesuai. Tes umum adalah apakah ada file atau satu nomor sama dengan yang lain.

  2. if [[ condition ]]

    Ini adalah variasi baru yang ditingkatkan pada testdari ksh yang juga mendukung bash dan zsh . testPerintah ini juga menetapkan kode keluar dan ifpernyataan bertindak sesuai. Di antara fitur-fiturnya yang diperluas, dapat menguji apakah string cocok dengan ekspresi reguler.

  3. if ((condition))

    Lain ksh ekstensi yang menampar dan zsh juga mendukung. Ini melakukan aritmatika. Sebagai hasil dari aritmatika, kode keluar diatur dan ifpernyataan bertindak sesuai. Ini mengembalikan kode keluar dari nol (benar) jika hasil perhitungan aritmatika adalah nol. Seperti [[...]], formulir ini bukan POSIX dan karenanya tidak portabel.

  4. if (command)

    Ini menjalankan perintah dalam sebuah subkulit. Ketika perintah selesai, itu menetapkan kode keluar dan ifpernyataan itu bertindak sesuai.

    Alasan khas untuk menggunakan subkulit seperti ini adalah untuk membatasi efek samping commandjika commanddiperlukan tugas variabel atau perubahan lain ke lingkungan shell. Perubahan tersebut tidak tetap setelah subkulit selesai.

  5. if command

    perintah dieksekusi dan ifpernyataan itu bertindak sesuai dengan kode keluarnya.

John1024
sumber
24
Terima kasih telah memasukkan opsi ke-5. Itulah kunci untuk memahami bagaimana ini sebenarnya bekerja dan secara mengejutkan kurang dimanfaatkan.
anak ayam
4
Perhatikan bahwa [sebenarnya biner, bukan perintah atau simbol internal. Umumnya tinggal di /bin.
Julien R.
8
@ JulienR. sebenarnya [adalah built in, sebagaimana adanya test. Ada versi biner yang tersedia untuk alasan kompatibilitas. Lihat help [dan help test.
OldTimer
4
Perlu dicatat bahwa sementara ((bukan POSIX, $((yaitu ekspansi aritmatika adalah dan mudah untuk membingungkan mereka. Seringkali solusi adalah dengan menggunakan sesuatu seperti [ $((2+2)) -eq 4 ]menggunakan aritmatika dalam pernyataan conditinal
Sergiy Kolodyazhnyy
1
Saya berharap saya dapat memilih suara ini lebih dari satu kali. Penjelasan sempurna.
Anthony Gatlin
77
  • (…)tanda kurung menunjukkan subkulit . Apa yang ada di dalamnya bukan ekspresi seperti dalam banyak bahasa lain. Ini daftar perintah (seperti tanda kurung di luar). Perintah-perintah ini dieksekusi dalam subproses terpisah, sehingga setiap pengalihan, penugasan, dll yang dilakukan di dalam tanda kurung tidak memiliki efek di luar tanda kurung.
    • Dengan tanda dolar terkemuka, $(…)adalah substitusi perintah : ada perintah di dalam tanda kurung, dan output dari perintah digunakan sebagai bagian dari baris perintah (setelah ekspansi tambahan kecuali substitusi berada di antara tanda kutip ganda, tapi itu cerita lain ) .
  • { … }kurung kurawal seperti tanda kurung di mana mereka mengelompokkan perintah, tetapi mereka hanya mempengaruhi penguraian, bukan pengelompokan. Program x=2; { x=4; }; echo $xmencetak 4, sedangkan x=2; (x=4); echo $xcetakan 2. (Juga tanda kurung menjadi kata kunci perlu dibatasi dan ditemukan dalam posisi perintah (maka spasi setelah {dan ;sebelumnya }) sedangkan tanda kurung tidak. Itu hanya sebuah sintaks sintaksis.)
    • Dengan tanda dolar terkemuka, ${VAR}adalah ekspansi parameter , yang diperluas ke nilai variabel, dengan kemungkinan transformasi tambahan. The ksh93shell juga mendukung ${ cmd;}sebagai bentuk perintah substitusi yang tidak menelurkan subkulit.
  • ((…))tanda kurung ganda mengelilingi instruksi aritmatika , yaitu, perhitungan pada bilangan bulat, dengan sintaksis yang menyerupai bahasa pemrograman lain. Sintaks ini sebagian besar digunakan untuk penugasan dan dalam kondisi. Ini hanya ada di ksh / bash / zsh, bukan di sh polos.
    • Sintaks yang sama digunakan dalam ekspresi aritmatika $((…)), yang diperluas ke nilai integer ekspresi.
  • [ … ]kurung tunggal mengelilingi ekspresi bersyarat . Ekspresi bersyarat sebagian besar dibangun pada operator seperti -n "$variable"untuk menguji apakah suatu variabel kosong dan -e "$file"untuk menguji apakah ada file. Perhatikan bahwa Anda memerlukan ruang di sekitar setiap operator (mis [ "$x" = "$y" ]. Tidak [ "$x"="$y" ]), dan spasi atau karakter seperti ;di dalam dan di luar tanda kurung (misalnya [ -n "$foo" ], tidak [-n "$foo"]).
  • [[ … ]]kurung ganda adalah bentuk alternatif dari ekspresi kondisional dalam ksh / bash / zsh dengan beberapa fitur tambahan, misalnya Anda dapat menulis [[ -L $file && -f $file ]]untuk menguji apakah suatu file merupakan tautan simbolis ke file biasa sedangkan kurung tunggal memerlukan [ -L "$file" ] && [ -f "$file" ]. Lihat Mengapa ekspansi parameter dengan spasi tanpa tanda kutip bekerja di dalam tanda kurung ganda [[tetapi bukan tanda kurung tunggal [? untuk lebih lanjut tentang topik ini.

Dalam shell, setiap perintah adalah perintah kondisional: setiap perintah memiliki status pengembalian yang 0 menunjukkan keberhasilan atau bilangan bulat antara 1 dan 255 (dan berpotensi lebih banyak di beberapa shell) menunjukkan kegagalan. The [ … ]perintah (atau [[ … ]]bentuk sintaks) adalah perintah tertentu yang juga bisa dibilang test …dan berhasil bila file ada, atau ketika string tidak kosong, atau ketika angka lebih kecil dari yang lain, dll ((…))bentuk sintaks berhasil ketika sejumlah bukan nol. Berikut adalah beberapa contoh kondisional dalam skrip shell:

  • Tes jika myfileberisi string hello:

    if grep -q hello myfile; then 
  • Jika mydirdirektori, ubah dan lakukan hal-hal:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
    
  • Uji apakah ada file yang dipanggil myfiledi direktori saat ini:

    if [ -e myfile ]; then 
  • Hal yang sama, tetapi juga termasuk tautan simbolik yang menjuntai:

    if [ -e myfile ] || [ -L myfile ]; then 
  • Tes jika nilai x(yang dianggap numerik) setidaknya 2, dengan mudah:

    if [ "$x" -ge 2 ]; then 
  • Tes jika nilai x(yang dianggap numerik) setidaknya 2, dalam bash / ksh / zsh:

    if ((x >= 2)); then 
Gilles
sumber
Perhatikan bahwa braket tunggal mendukung -aalih - alih &&, sehingga orang dapat menulis:, [ -L $file -a -f $file ]yang merupakan jumlah karakter yang sama dalam tanda kurung tanpa tambahan [dan ]...
Alexis Wilke
6
@AlexisWilke Para operator -adan -obermasalah karena mereka dapat menyebabkan parse yang salah jika beberapa operan yang terlibat terlihat seperti operator. Itu sebabnya saya tidak menyebut mereka: mereka tidak memiliki keunggulan dan tidak selalu berhasil. Dan jangan pernah menulis ekspansi variabel tanpa tanda kutip tanpa alasan yang baik: [[ -L $file -a -f $file ]]baik-baik saja tetapi dengan tanda kurung tunggal yang Anda butuhkan [ -L "$file" -a -f "$file" ](yang ok misalnya jika $fileselalu dimulai dengan /atau ./).
Gilles
Perhatikan bahwa itu [[ -L $file && -f $file ]](tidak -adengan [[...]]varian).
Stéphane Chazelas
18

Dari dokumentasi bash :

(list)daftar dieksekusi dalam lingkungan subkulit (lihat LINGKUNGAN PERINTAH EKSEKUSI di bawah). Penugasan variabel dan perintah bawaan yang memengaruhi lingkungan shell tidak tetap berlaku setelah perintah selesai. Status kembali adalah status keluar dari daftar.

Dengan kata lain, Anda memastikan bahwa apa pun yang terjadi di 'daftar' (seperti a cd) tidak berpengaruh di luar (dan ). Satu-satunya hal yang akan bocor adalah kode keluar dari perintah terakhir atau dengan set -eperintah pertama yang menghasilkan kesalahan (selain beberapa seperti if, while, dll)

((expression))Ekspresi dievaluasi sesuai dengan aturan yang dijelaskan di bawah ini di bawah EVALUASI ARITHMETIC. Jika nilai ekspresi bukan nol, status pengembalian adalah 0; jika tidak, status pengembaliannya adalah 1. Ini persis sama dengan membiarkan "ekspresi".

Ini adalah ekstensi bash yang memungkinkan Anda melakukan matematika. Ini agak mirip dengan menggunakan exprtanpa semua keterbatasan expr(seperti memiliki ruang di mana-mana, melarikan diri *, dll.)

[[ expression ]]Kembalikan status 0 atau 1 tergantung pada evaluasi ekspresi ekspresi bersyarat. Ekspresi terdiri dari kata-kata dasar yang dijelaskan di bawah ini di bawah EKSPRESI KONDISI. Pemisahan kata dan perluasan pathname tidak dilakukan pada kata-kata antara [[dan]]; ekspansi tilde, ekspansi parameter dan variabel, ekspansi aritmatika, substitusi perintah, substitusi proses, dan penghapusan kutipan dilakukan. Operator bersyarat seperti -f harus tanda kutip untuk diakui sebagai primer.

Saat digunakan dengan [[, operator <dan> mengurutkan secara leksikografis menggunakan lokal saat ini.

Ini menawarkan tes lanjutan untuk membandingkan string, angka, dan file sedikit seperti testpenawaran, tetapi lebih kuat.

[ expr ]Kembalikan status 0 (benar) atau 1 (salah) tergantung pada evaluasi ekspresi bersyarat expr. Setiap operator dan oper dan harus argumen yang terpisah. Ekspresi terdiri dari kata-kata pendahuluan yang dijelaskan di atas di bawah EKSPRESI KONDISI. tes tidak menerima opsi apa pun, juga tidak menerima dan mengabaikan argumen - sebagai menandakan akhir opsi.

[...]

Yang ini panggilan test. Sebenarnya, di masa lalu, [adalah tautan simbolis ke test. Ini bekerja dengan cara yang sama dan Anda memiliki keterbatasan yang sama. Karena biner mengetahui nama yang digunakannya untuk memulai, program uji dapat mengurai parameter sampai ia menemukan parameter ]. Trik Unix yang menyenangkan.

Perhatikan bahwa dalam kasus bash, [dan testmerupakan fungsi bawaan (seperti yang disebutkan dalam komentar), namun batasan yang hampir sama berlaku.

Alexis Wilke
sumber
1
Meskipun testdan [tentu saja dibangun dalam perintah di Bash, tetapi kemungkinan biner eksternal juga ada.
ilkkachu
1
Biner eksternal untuk [bukan merupakan tautan simbolis ke testpada kebanyakan sistem modern.
Random832
1
Entah bagaimana, saya merasa lucu bahwa mereka bersusah payah untuk membuat dua binari yang terpisah, yang sama-sama memiliki apa yang mereka butuhkan, alih-alih hanya menggabungkan mereka dan menambahkan beberapa persyaratan. Meskipun sebenarnya strings /usr/bin/testmenunjukkan itu memiliki teks bantuan juga, jadi saya tidak tahu harus berkata apa.
ilkkachu
2
@ Random832 Saya mendapatkan poin Anda tentang alasan GNU untuk menghindari perilaku arg0 yang tidak terduga tetapi tentang persyaratan POSIX, saya tidak akan begitu setuju. Sementara testperintah jelas diperlukan untuk ada sebagai perintah berbasis file mandiri oleh standar, tidak ada di dalamnya menyatakan [variannya perlu diimplementasikan dengan cara itu juga. Sebagai contoh, Solaris 11 tidak menyediakan yang [dapat dieksekusi tetapi tetap sepenuhnya sesuai dengan standar POSIX
jlliagre
2
(keluar 1) memiliki efek di luar tanda kurung.
Alexander
14

[ vs. [[

Jawaban ini akan mencakup [vs [[subset dari pertanyaan.

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, <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 =~.

      Dalam Bashese: [adalah perintah bawaan , dan [[merupakan kata kunci: https://askubuntu.com/questions/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 by
    • [ a = a ] && [ b = b ]: POSIX dan setara yang dapat diandalkan
  • (

    • [[ (a = a || a = b) && a = b ]]: Salah
    • [ ( a = a ) ]: kesalahan sintaksis, ()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 5
  • 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 4 , kehilangan sihir dengan''
    • [[ ab? =~ 'ab?' ]]: benar
  • =~

    • [[ ab =~ ab? ]]: true, POSIX memperpanjang kecocokan ekspresi reguler , ?tidak glob berkembang
    • [ a =~ a ]: kesalahan sintaks. Tidak 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 (.

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

5 meskipun pengelompokan (di sini dengan {...;}kelompok 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
@ StéphaneChazelas terima kasih atas informasinya! Saya telah menambahkan exprjawabannya. Istilah "ekstensi Bash" tidak dimaksudkan untuk menyiratkan Bash adalah shell pertama yang menambahkan beberapa sintaks, belajar POSIX sh vs Bash sudah cukup untuk membuatku gila.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Lihat man testapakah Anda mencoba man [dan tersesat. Itu akan menjelaskan varian POSIX.
Jonathan Komar
13

Beberapa contoh:

Tes tradisional:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

testdan [perintah seperti yang lain, sehingga variabel ini dibagi menjadi kata-kata kecuali dalam tanda kutip.

Tes gaya baru

[[ ... ]] adalah konstruksi shell khusus (yang lebih baru), yang bekerja sedikit berbeda, hal yang paling jelas adalah bahwa ia tidak menggunakan variabel pemisah kata:

if [[ -n $foo ]] ; then... 

Beberapa dokumentasi tentang [dan di [[sini .

Tes aritmatika:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

Perintah "normal":

Semua tindakan di atas seperti perintah normal, dan ifdapat mengambil perintah apa pun:

# grep returns true if it finds something
if grep pattern file ; then ...

Banyak perintah:

Atau kita bisa menggunakan banyak perintah. Membungkus seperangkat perintah dalam ( ... )menjalankannya dalam subkulit, membuat salinan sementara dari status shell (direktori kerja, variabel). Jika kita perlu menjalankan beberapa program sementara di direktori lain:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...
ilkkachu
sumber
1

Perintah Pengelompokan

Bash menyediakan dua cara untuk mengelompokkan daftar perintah yang akan dieksekusi sebagai satu unit.

( list )Menempatkan daftar perintah di antara tanda kurung menyebabkan lingkungan subkulit dibuat, dan masing-masing perintah dalam daftar dieksekusi di subkulit itu. Karena daftar dieksekusi dalam subkulit, tugas variabel tidak tetap berlaku setelah subkulit selesai.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; }Menempatkan daftar perintah antara kurung kurawal menyebabkan daftar dieksekusi dalam konteks shell saat ini . Tidak ada subkulit yang dibuat. Tanda titik koma (atau baris baru) diperlukan. Sumber

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Konstruksi Bersyarat

Braket Tunggal yaitu []
Untuk perbandingan ==, !=, <,dan >dan harus digunakan dan untuk perbandingan numerik eq, ne,ltdan gtharus digunakan.

Yaitu Kurung yang Ditingkatkan[[]]

Dalam semua contoh di atas, kami hanya menggunakan tanda kurung tunggal untuk melampirkan ekspresi bersyarat, tetapi bash memungkinkan tanda kurung ganda yang berfungsi sebagai versi yang disempurnakan dari sintaks braket tunggal.

Untuk perbandingan ==, !=, <,dan >dapat digunakan secara harfiah.

  • [adalah sinonim untuk perintah tes. Bahkan jika itu dibangun ke shell itu menciptakan proses baru.
  • [[ adalah versi baru yang disempurnakan, yang merupakan kata kunci, bukan program.
  • [[dipahami oleh Korndan Bash.

Sumber

Premraj
sumber