Diutamakan dari operator logika shell &&, ||

126

Saya mencoba untuk memahami bagaimana presedensi operator logis bekerja di bash. Sebagai contoh, saya akan berharap, bahwa perintah berikut tidak menggemakan apa pun.

true || echo aaa && echo bbb

Namun, bertentangan dengan harapan saya, bbbdicetak.

Bisakah seseorang tolong jelaskan, bagaimana saya bisa memahami senyawa &&dan ||operator di bash?

Martin Vegter
sumber

Jawaban:

127

Dalam banyak bahasa komputer, operator dengan prioritas yang sama bersifat asosiatif kiri . Artinya, dengan tidak adanya struktur pengelompokan, operasi paling kiri dijalankan terlebih dahulu. Bash tidak terkecuali pada aturan ini.

Ini penting karena, dalam Bash, &&dan ||memiliki prioritas yang sama.

Jadi yang terjadi dalam contoh Anda adalah bahwa operasi paling kiri ( ||) dilakukan terlebih dahulu:

true || echo aaa

Karena truejelas benar, ||hubung singkat operator dan seluruh pernyataan dianggap benar tanpa perlu mengevaluasi echo aaaseperti yang Anda harapkan. Sekarang tinggal melakukan operasi paling kanan:

(...) && echo bbb

Karena operasi pertama dievaluasi menjadi true (yaitu memiliki status keluar 0), seolah-olah Anda sedang mengeksekusi

true && echo bbb

sehingga &&tidak akan mengalami hubungan pendek, itulah sebabnya Anda lihat bbbbergema.

Anda akan mendapatkan perilaku yang sama dengannya

false && echo aaa || echo bbb

Catatan berdasarkan komentar

  • Anda harus mencatat bahwa aturan associativity kiri hanya diikuti ketika kedua operator memiliki prioritas yang sama . Hal ini tidak terjadi ketika Anda menggunakan operator ini dalam hubungannya dengan kata kunci seperti [[...]]atau ((...))atau gunakan -odan -aoperator sebagai argumen untuk testatau [perintah. Dalam kasus seperti itu, AND ( &&atau -a) diutamakan daripada OR ( ||atau -o). Terima kasih atas komentar Stephane Chazelas karena telah menjelaskan poin ini.
  • Tampaknya dalam bahasa C dan C-like &&memiliki prioritas lebih tinggi daripada ||yang mungkin mengapa Anda mengharapkan konstruksi asli Anda berperilaku seperti

    true || (echo aaa && echo bbb). 

    Namun, ini tidak terjadi pada Bash, di mana kedua operator memiliki prioritas yang sama, itulah sebabnya Bash mem-parsing ekspresi Anda menggunakan aturan associativity kiri. Terima kasih atas komentar Kevin karena mengemukakan ini.

  • Mungkin juga ada kasus di mana ketiga ekspresi dievaluasi. Jika perintah pertama mengembalikan status keluar bukan nol, ||tidak akan terjadi hubungan pendek dan melanjutkan untuk mengeksekusi perintah kedua. Jika perintah kedua kembali dengan status keluar nol, maka &&tidak akan terjadi hubungan pendek juga dan perintah ketiga akan dieksekusi. Terima kasih atas komentar Ignacio Vazquez-Abrams karena mengemukakan ini.

Joseph R.
sumber
26
Sebuah catatan untuk menambah sedikit kebingungan: sementara operator &&dan ||shell seperti cmd1 && cmd2 || cmd3memiliki prioritas yang sama, &&in ((...))dan [[...]]memiliki prioritas lebih dari ||( ((a || b && c))is ((a || (b && c)))). Sama berlaku untuk -a/ -odalam test/ [dan finddan &/ |dalam expr.
Stéphane Chazelas
16
Dalam bahasa c-like, &&lebih diutamakan daripada ||, sehingga perilaku yang diharapkan OP akan terjadi. Pengguna yang tidak terbiasa dengan bahasa tersebut mungkin tidak menyadari bahwa dalam bash mereka memiliki prioritas yang sama, jadi mungkin lebih baik menunjukkan secara eksplisit bahwa mereka memiliki prioritas yang sama dalam bash.
Kevin
Perhatikan juga bahwa ada situasi di mana ketiga perintah dapat dijalankan, yaitu jika perintah tengah dapat mengembalikan benar atau salah.
Ignacio Vazquez-Abrams
@Kevin Silakan periksa jawaban yang diedit.
Joseph R.
1
Hari ini, bagi saya, hit Google teratas untuk "bash operator precedence" adalah tldp.org/LDP/abs/html/opprecedence.html ... yang mengklaim bahwa && memiliki prioritas lebih tinggi daripada ||. Namun @ Josephos. jelas benar de facto dan de jure juga. Dash berperilaku sama dan mencari "diutamakan" di pubs.opengroup.org/onlinepubs/009695399/utilities/… , saya melihat ini persyaratan POSIX, jadi kita bisa bergantung padanya. Semua yang saya temukan untuk pelaporan bug adalah alamat email penulis, yang pasti telah dipecat hingga mati dalam enam tahun terakhir. Saya akan tetap mencoba ...
Martin Dorey
62

Jika Anda ingin banyak hal bergantung pada kondisi Anda, kelompokkan:

true || { echo aaa && echo bbb; }

Itu tidak mencetak apa-apa, sementara

true && { echo aaa && echo bbb; }

mencetak kedua string.


Alasan mengapa hal ini terjadi jauh lebih sederhana daripada yang dilakukan Joseph. Ingat apa yang dilakukan Bash dengan ||dan &&. Ini semua tentang status pengembalian perintah sebelumnya. Cara literal untuk melihat perintah mentah Anda adalah:

( true || echo aaa ) && echo bbb

Perintah pertama ( true || echo aaa) keluar dengan 0.

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
Oli
sumber
7
+1 Untuk mencapai hasil yang diinginkan OP. Perhatikan bahwa tanda kurung yang Anda tempatkan (true || echo aaa) && echo bbbpersis seperti yang saya hasilkan.
Joseph R.
1
Senang melihat @Oli di sisi dunia ini.
Braiam
7
Perhatikan semi-kolom ;di bagian paling akhir. Tanpa ini, itu tidak akan berhasil!
Serge Stroobandt
2
Saya mengalami masalah dengan ini karena saya kehilangan semi-kolon setelah perintah terakhir (misalnya echo bbb;dalam contoh pertama). Setelah saya tahu bahwa saya kehilangan itu, jawaban ini persis apa yang saya cari. +1 untuk membantu saya mencari tahu bagaimana mencapai apa yang saya inginkan!
Doktor J
5
Layak dibaca untuk siapa pun yang bingung oleh (...)dan {...}notasi: petunjuk pengelompokan perintah
ANTARA
16

The &&dan ||operator tidak membalas penggantian inline untuk if-then-else. Meskipun jika digunakan dengan hati-hati, mereka dapat melakukan banyak hal yang sama.

Sebuah tes tunggal mudah dan jelas ...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

Namun, mencoba menambahkan beberapa tes dapat menghasilkan hasil yang tidak terduga ...

[[ A == A ]]  && echo TRUE   || echo FALSE          # TRUE  (as expected)
[[ A == B ]]  && echo TRUE   || echo FALSE          # FALSE (as expected)
[[ A == A ]]  || echo FALSE  && echo TRUE           # TRUE  (as expected)
[[ A == B ]]  || echo FALSE  && echo TRUE           # FALSE TRUE   (huh?)

Mengapa FALSE dan TRUE digemakan?

Apa yang terjadi di sini adalah bahwa kita tidak menyadari itu &&dan ||adalah operator kelebihan beban yang bertindak berbeda di dalam kurung uji bersyarat [[ ]]daripada yang mereka lakukan dalam daftar DAN dan ATAU (eksekusi bersyarat) yang kita miliki di sini.

Dari halaman bash (diedit) ...

Daftar

Daftar adalah urutan satu atau beberapa jaringan pipa yang dipisahkan oleh salah satu operator;, &, &&, atau ││, dan secara opsional diakhiri oleh salah satu dari,, &, atau. Dari daftar operator ini, && dan ││ memiliki prioritas yang sama, diikuti oleh; dan &, yang memiliki prioritas sama.

Urutan satu atau lebih baris baru mungkin muncul dalam daftar alih-alih tanda titik koma untuk membatasi perintah.

Jika perintah diakhiri oleh operator kontrol &, shell mengeksekusi perintah di latar belakang dalam sebuah subkulit. Shell tidak menunggu perintah selesai, dan status kembali adalah 0. Perintah dipisahkan oleh a; dieksekusi secara berurutan; shell menunggu setiap perintah berakhir pada gilirannya. Status kembali adalah status keluar dari perintah terakhir yang dijalankan.

Daftar AND dan OR adalah urutan dari salah satu dari lebih banyak pipa yang dipisahkan oleh operator kontrol&& dan ││. Daftar AND dan OR dijalankan dengan asosiasi kiri.

Daftar AND memiliki formulir ...
command1 && command2
Command2 dijalankan jika, dan hanya jika, command1 mengembalikan status keluar nol.

Daftar ATAU memiliki bentuk ...
command1 ││ command2
Command2 dijalankan jika dan hanya jika command1 mengembalikan status keluar yang tidak nol.

Status pengembalian daftar AND dan OR adalah status keluar dari perintah terakhir yang dijalankan dalam daftar.

Kembali ke contoh terakhir kami ...

[[ A == B ]]  || echo FALSE  && echo TRUE

[[ A == B ]]  is false

     ||       Does NOT mean OR! It means...
              'execute next command if last command return code(rc) was false'

 echo FALSE   The 'echo' command rc is always true
              (i.e. it successfully echoed the word "FALSE")

     &&       Execute next command if last command rc was true

 echo TRUE    Since the 'echo FALSE' rc was true, then echo "TRUE"

Baik. Jika itu benar, lalu mengapa contoh berikutnya hingga terakhir menggemakan sesuatu?

[[ A == A ]]  || echo FALSE  && echo TRUE


[[ A == A ]]  is true

     ||       execute next command if last command rc was false.

 echo FALSE   Since last rc was true, shouldn't it have stopped before this?
                Nope. Instead, it skips the 'echo FALSE', does not even try to
                execute it, and continues looking for a `&&` clause.

     &&       ... which it finds here

 echo TRUE    ... so, since `[[ A == A ]]` is true, then it echos "TRUE"

Risiko kesalahan logika saat menggunakan lebih dari satu &&atau ||dalam daftar perintah cukup tinggi.

Rekomendasi

Satu &&atau ||dalam daftar perintah berfungsi seperti yang diharapkan sehingga cukup aman. Jika ini adalah situasi di mana Anda tidak memerlukan klausa lain, sesuatu seperti yang berikut ini bisa lebih jelas untuk diikuti (kurung kurawal diperlukan untuk mengelompokkan 2 perintah terakhir) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

Banyak &&dan ||operator, di mana setiap perintah kecuali yang terakhir adalah tes (yaitu di dalam tanda kurung [[ ]]), biasanya juga aman karena semua tetapi operator terakhir berperilaku seperti yang diharapkan. Operator terakhir bertindak lebih seperti a thenatau elseklausa.

DocSalvager
sumber
0

Saya juga bingung dengan ini, tetapi beginilah cara saya berpikir tentang cara Bash membaca pernyataan Anda (saat membaca simbol dari kiri ke kanan):

  1. Simbol yang ditemukan true. Ini perlu dievaluasi setelah akhir perintah tercapai. Pada titik ini, tidak tahu apakah ada argumen. Simpan perintah dalam buffer eksekusi.
  2. Simbol yang ditemukan ||. Perintah sebelumnya sekarang selesai, jadi evaluasilah. Command (buffer) dieksekusi: true. Hasil evaluasi: 0 (yaitu kesuksesan). Simpan hasil 0 dalam register "evaluasi terakhir". Sekarang perhatikan simbol ||itu sendiri. Ini tergantung pada hasil evaluasi terakhir yang bukan nol. Memeriksa register "evaluasi terakhir" dan menemukan 0. Karena 0 bukan bukan nol, perintah berikut ini tidak perlu dievaluasi.
  3. Simbol yang ditemukan echo. Dapat mengabaikan simbol ini, karena perintah berikut ini tidak perlu dievaluasi.
  4. Simbol yang ditemukan aaa. Ini adalah argumen untuk perintah echo(3), tetapi karena echo(3) tidak perlu dievaluasi, ini dapat diabaikan.
  5. Simbol yang ditemukan &&. Ini tergantung pada hasil evaluasi terakhir menjadi nol. Memeriksa register "evaluasi terakhir" dan menemukan 0. Karena 0 adalah nol, perintah berikut ini perlu dievaluasi.
  6. Simbol yang ditemukan echo. Perintah ini perlu dievaluasi setelah akhir perintah tercapai, karena perintah berikut memang perlu dievaluasi. Simpan perintah dalam buffer eksekusi.
  7. Simbol yang ditemukan bbb. Ini adalah argumen untuk perintah echo(6). Karena echomemang perlu dievaluasi, tambahkan bbbke eksekusi buffer.
  8. Mencapai ujung garis. Perintah sebelumnya sekarang lengkap dan memang perlu dievaluasi. Command (buffer) dieksekusi: echo bbb. Hasil evaluasi: 0 (yaitu kesuksesan). Simpan hasil 0 dalam register "evaluasi terakhir".

Dan tentu saja, langkah terakhir menyebabkan bbbdigemakan ke konsol.

Kidburla
sumber