Apakah tes atau [atau [lebih portable antara shell bash dan antara shell lain?

44

Saya tahu saya bisa melakukannya

$ [ -w /home/durrantm ] && echo "writable"
writable

atau

$ test -w /home/durrantm && echo "writable"
writable

atau

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Saya suka menggunakan sintaks ketiga. Apakah mereka setara dalam semua hal dan untuk semua kasus negatif dan tepi? Apakah ada perbedaan dalam portabilitas, mis. Antara bash di Ubuntu dan pada OS X atau versi bash yang lebih lama / lebih baru, mis. Sebelum / sesudah 4.0 dan apakah keduanya memperluas ekspresi dengan cara yang sama?

Michael Durrant
sumber
2
Untuk [ … ]vs [[ … ]]vs test …, ada jawaban yang lebih lengkap di sebagian besar pertanyaan duplikat ini .
Gilles 'SANGAT berhenti menjadi jahat'
Untuk masalah spesifik pengujian untuk kemampuan menulis suatu file, lihat juga Bagaimana cara menguji akses tulis ke file secara non-invasif?
G-Man Mengatakan 'Reinstate Monica'
1
Ada pepatah: "tidak ada kode portabel, hanya kode yang telah porting". Saran saya mengenai hal ini: Gunakan formulir yang paling mudah dibaca (mungkin [[...]]) dan coba di semua platform yang ingin Anda dukung. Tidak banyak digunakan dalam mengaburkan skrip Anda sehingga skrip berjalan pada platform kuno yang tidak Anda maupun audiens target Anda gunakan. Itu hanya akan membuat kode Anda sulit dibaca, memperkenalkan bug yang tidak perlu dan bahkan mungkin masalah keamanan (seperti yang terjadi pada openssl).
stefan.schwetschke

Jawaban:

31

[adalah sinonim dari testperintah dan secara bersamaan perintah bash builtin dan terpisah. Tapi [[adalah kata kunci pesta dan bekerja di beberapa versi saja. Jadi untuk alasan portabilitas Anda lebih baik menggunakan satu []atautest

[ -w "/home/durrantm" ] && echo "writable"
Costas
sumber
2
[itu POSIX builtin atau Bash builtin?
Sandburg
@Sandburg POSIX adalah standar, sehingga tidak dapat memiliki built-in. Itu tidak menentukan apakah sesuatu harus dibangun ke dalam penerjemah atau tidak, hanya apa yang harus dilakukan. Aturan-aturan ini berlaku untuk kedua bentuk, testdan [sama. Jika sebuah shell dapat melakukan evaluasi sendiri, itu menghemat proses Anda dan membuatnya agak lebih cepat, tetapi hasilnya harus sama.
Bachsau
@ Bachsau yang masih belum menjawab pertanyaan Sandburg, apakah perilaku [didefinisikan oleh POSIX dan karena itu portabel secara maksimal (yang tampaknya menjadi inti dari pertanyaan ini jika saya tidak salah)
JamesTheAwesomeDude
@ Sandburg (dan kepada siapa pun yang tersandung di sini): Ya, sepertinya keduanya [dan test POSIX . Tampaknya tidak ada "yang direkomendasikan", meskipun satu-satunya perbedaan (?) Yang dapat saya temukan di antara mereka adalah bahwa test"tidak akan mengenali argumen" - "[sebagai pembatas yang mengindikasikan akhir opsi]" (? - menyiratkan bahwa " -" yang diakui sebagai end-of-argumen untuk [, yang saya tidak bisa benar-benar datang dengan kasus tes yang baik untuk tetap tapi saya ngelantur Gunakan yang Anda akan IMO,... [terlihat elegan, tapi testisclearly perintah)
JamesTheAwesomeDude
35

Ya, ada perbedaan. Yang paling portabel adalah testatau [ ]. Ini adalah bagian dari spesifikasi POSIXtest .

The if ... fikonstruk juga ditentukan oleh POSIX dan harus benar-benar portabel.

Ini [[ ]]adalah kshfitur yang juga hadir dalam beberapa versi bash( semua yang modern ), di zshdan mungkin dalam yang lain tetapi tidak hadir di shatau dashatau berbagai cangkang sederhana lainnya.

Jadi, untuk membuat skrip Anda portabel, gunakan [ ], testatau if ... fi.

terdon
sumber
10
Hanya untuk dicatat, bashdan zshtelah mendukung [[untuk waktu yang sangat lama ( bashmenambahkannya di akhir 90-an, zshpaling lambat tahun 2000, dan saya akan terkejut jika itu pernah kekurangan dukungan), jadi Anda tidak mungkin menemukan versi keduanya tanpa [[. Mengalami shell yang sesuai dengan POSIX (seperti dash) jauh lebih mungkin.
chepner
1
Salah satu fitur menarik [[adalah bahwa ekspansi parameter tidak perlu dikutip: [[-f $file]]event berfungsi jika $fileberisi karakter spasi.
helpermethod
2
@helpermethod juga, builtin regular expressions[[ $a =~ ^reg.*exp.*$' ]]
GnP
20

Harap dicatat, itu [] && cmdtidak sama dengan if .. fikonstruksi.

Terkadang perilakunya sangat mirip dan Anda dapat menggunakannya [] && cmdsebagai gantinya if .. fi. Tapi hanya terkadang. Jika Anda memiliki lebih dari satu perintah untuk mengeksekusi jika kondisi atau Anda perlu if .. else .. fihati-hati dan whatch logikanya.

Beberapa contoh:

[ -z "$VAR" ] && ls file || echo wiiii

Tidak sama dengan

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

karena jika lsakan gagal, echoakan dieksekusi yang tidak akan terjadi dengan if.

Contoh lain:

[ -z "$VAR" ] && ls file && echo wiii

tidak sama dengan

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

meskipun konstruksi ini akan bertindak sama

[ -z "$VAR" ] && { ls file ; echo wiii ; }

harap diperhatikan ;setelah gema penting dan harus ada di sana.

Jadi melanjutkan pernyataan di atas bisa kita katakan

[] && cmd == jika perintah pertama berhasil maka jalankan perintah berikutnya

if .. fi == jika kondisi (yang mungkin merupakan perintah tes juga) maka jalankan perintah

Jadi untuk portabilitas antara [dan hanya [[digunakan [.

ifkompatibel dengan POSIX. Jadi, jika Anda harus memilih di antara [dan ifmemilih melihat tugas Anda dan perilaku yang diharapkan.

buru-buru
sumber
Saya mungkin hanya menjadi padat, tetapi saya tidak melihat apa bedanya ... bisakah Anda memberi contoh di mana kedua konstruksi akan memberikan hasil yang berbeda?
evilsoup
1
@ evilsoup, diperbarui. Mungkin saya bukan penjelajah terbaik, meskipun saya harap ini akan menjadi jelas sekarang.
buru
1
Poin yang sangat bagus, buru-buru. Saya kira bentuk yang aman dari contoh 1 Anda akan: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Ini sedikit lebih bertele-tele, tetapi masih lebih pendek dari if...fikonstruksi.
PM 2Ring
Menjadikan pertanyaan tentang [vs [[vs tes dan bukan juga tentang ... fi vs && untuk menjadikannya SATU pertanyaan. Sayangnya melakukan itu membuat jawaban ini tampak tidak penting. Permintaan maaf karena tidak mendapatkan pertanyaan yang awalnya berfokus pada hal ini. Langsung dan (coba) belajar. :)
Michael Durrant
Saya tidak memilih karena a) ini terutama jawaban yang baik, tetapi itu adalah jawaban untuk pertanyaan yang berbeda. b) Anda hadir [dan ifsebagai pengganti, tetapi tidak. Ini sebenarnya &&yang menggantikan if. [mengeksekusi beberapa kode dan mengembalikan status, sangat suka lsdan grepmau. ifeksekusi cabang tergantung pada status pengembalian perintah (pernyataan) yang diberikan setelah if, bisa berupa perintah (pernyataan) apa pun. &&mengeksekusi pernyataan berikutnya hanya jika yang sebelumnya mengembalikan 0, seperti sederhana if..then..fi.
GnP
9

Sebenarnya &&yang menggantikan if, bukan test: ifpernyataan dalam tes skrip shell menguji apakah perintah mengembalikan status keluar "berhasil" (nol); dalam contoh Anda, perintahnya adalah [.

Jadi, sebenarnya ada dua hal yang berbeda di sini: perintah yang digunakan untuk menjalankan tes, dan sintaks yang digunakan untuk mengeksekusi kode berdasarkan hasil tes itu.

Perintah uji:

  • testadalah perintah standar untuk mengevaluasi properti string dan file; dalam contoh Anda, Anda menjalankan perintahtest -w /home/durrantm
  • [adalah alias dari perintah itu, sama-sama terstandarisasi, yang memiliki argumen terakhir wajib ]agar terlihat seperti ekspresi kurung; jangan tertipu, itu masih hanya sebuah perintah (Anda bahkan mungkin menemukan bahwa sistem Anda memiliki file bernama /bin/[)
  • [[adalah versi tambahan dari perintah uji yang dibangun ke dalam beberapa shell, tetapi bukan bagian dari standar POSIX yang sama; itu termasuk opsi tambahan yang tidak Anda gunakan di sini

Ekspresi bersyarat:

  • The &&operator ( standar di sini ) melakukan operasi logika AND, dengan mengevaluasi dua perintah dan kembali 0 (yang mewakili true) jika mereka berdua return 0; itu hanya akan mengevaluasi perintah kedua jika yang pertama mengembalikan nol, sehingga dapat digunakan sebagai kondisi sederhana
  • The if ... then ... fikonstruk ( standar di sini ) menggunakan metode yang sama menilai "kebenaran", tetapi memungkinkan untuk daftar senyawa pernyataan dalam thenklausa, daripada perintah tunggal diberikan oleh &&hubungan arus pendek, dan menyediakan elifdan elseklausa, yang sulit untuk menulis hanya menggunakan &&dan ||. Perhatikan bahwa tidak ada tanda kurung di sekitar kondisi dalam ifpernyataan .

Jadi, berikut ini semua sama-sama portabel, dan sepenuhnya setara, rendering dari contoh Anda:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Sementara yang berikut ini juga setara, tetapi kurang portabel karena sifat non-standar [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
IMSoP
sumber
Ya saya menghapus bagian if .... fi untuk menjadikan ini 1 pertanyaan.
Michael Durrant
7

Untuk portabilitas, gunakan test/ [. Tetapi jika Anda tidak membutuhkan portabilitas, demi kewarasan diri Anda dan orang lain yang membaca skrip Anda gunakan [[. :)

Lihat juga What is the difference between test, [ and [[ ?di BashFAQ .

PM 2Ring
sumber
7

Jika Anda ingin portabilitas di luar dunia seperti Bourne, maka:

test -w /home/durrantm && echo writable

adalah yang paling portabel. Ia bekerja di cangkang Bourne, cshdan rckeluarga.

test -w /home/durrantm && echo "writable"

akan output "writable"bukan writabledi kerang dari rckeluarga ( rc, es, akanga, di mana "tidak istimewa).

[ -w /home/durrantm ] && echo writable

tidak akan bekerja di shell cshatau rckeluarga pada sistem yang tidak memiliki [perintah $PATH(beberapa telah diketahui memiliki testtetapi tidak [alias).

if [ -w /home/durrantm ]; then echo writabe; fi

hanya bekerja di kulit keluarga Bourne.

[[ -w /home/durrantm ]] && echo writable

hanya bekerja di ksh(tempat asalnya), zshdan bash(ketiganya dalam keluarga Bourne).

Tidak ada yang akan bekerja di fishshell di mana Anda perlu:

[ -w /home/durrantm ]; and echo writable

atau:

if [ -w /home/durrantm ]; echo writable; end
Stéphane Chazelas
sumber
0

Alasan saya yang paling penting untuk memilih salah satu if foo; then bar; fiatau foo && barapakah status keluar dari seluruh perintah itu penting.

membandingkan:

#!/bin/sh
set -e
foo && bar
do_baz

dengan:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Anda mungkin berpikir mereka melakukan hal yang sama; namun jika foogagal (atau salah, tergantung sudut pandang Anda) maka pada contoh pertama do_baz tidak akan dieksekusi, karena skrip akan keluar ... Script set -emenginstruksikan shell untuk segera keluar jika ada perintah yang mengembalikan status palsu. Sangat berguna jika Anda melakukan hal-hal seperti:

cd /some/directory
rm -rf *

Anda tidak ingin skrip terus berjalan jika cdgagal karena alasan apa pun.

Wurtel
sumber
1
Tidak ada yang gagal footidak akan membatalkan skrip dalam kedua kasus. Itu adalah kasus khusus untuk set -e(ketika perintah dievaluasi sebagai kondisi (di sebelah kiri beberapa && / || atau jika / saat / sampai / elsif ... kondisi). Kegagalan barakan keluar dari shell dalam kedua kasus.
Stéphane Chazelas
2
Anda ingin cd /some/directory && rm -rf -- *atau cd /some/directory || exit; rm -rf -- *(masih tidak menghapus file tersembunyi). Saya pribadi tidak suka ide menggunakan set -esebagai alasan untuk tidak berusaha menulis kode yang benar.
Stéphane Chazelas