Kesalahan dalam tes braket shell ketika string adalah kurung-kiri

27

Dulu saya yakin tentang fakta bahwa mengutip string selalu merupakan praktik yang baik untuk menghindari shell mengurai itu.

Lalu saya menemukan ini:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Mencoba mengisolasi masalah, mendapatkan kesalahan yang sama:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Saya memecahkan masalah seperti ini:

[ "$x" = '1' ] && [ "$y" = '1' ]

Saya masih perlu tahu apa yang terjadi di sini.

Claudio
sumber
2
Sebagai solusi, dalam bash, Anda dapat menggunakan[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
Spesifikasi POSIX untuk pengujian secara eksplisit menggambarkan -adan -osebagai usang untuk alasan ini (yang merupakan [OB]superskrip di samping spesifikasi mereka). Jika Anda menulis [ "$x" = 1 ] && [ "$y" = 1 ]sebagai gantinya, Anda akan baik-baik saja, dan akan berada dalam ranah perilaku yang terdefinisi dengan baik / standar.
Charles Duffy
6
Inilah mengapa orang biasa menggunakan [ "x$x" = "x1" ]untuk mencegah argumen disalahartikan sebagai operator.
Jonathan Leffler
@JonathanLeffler: Hei, Anda memadatkan jawaban saya untuk satu kalimat, tidak adil! :) Jika seseorang menggunakan shell POSIX seperti dashdaripada Bash, itu masih merupakan praktik yang bermanfaat.
Nominal Animal
Saya ingin berterima kasih kepada semua orang yang meluangkan waktu untuk menjawab pertanyaan saya, teman-teman yang sangat saya hargai :)! Saya juga ingin berterima kasih atas penyuntingan penting yang dilakukan untuk pertanyaan saya. Memasuki forum ini kadang-kadang memberi saya sensasi yang sama untuk melarikan diri dari Alcatraz, langkah yang salah berarti hidup Anda. Pokoknya saya benar-benar berharap Terima kasih saya akan menghubungi Anda sebelum komentar ini dihapus
Claudio

Jawaban:

25

Ini adalah kasus sudut yang sangat tidak jelas yang dapat dianggap sebagai bug dalam cara tes [bawaan ditetapkan; Namun, itu cocok dengan perilaku [biner aktual yang tersedia di banyak sistem. Sejauh yang saya tahu, itu hanya mempengaruhi kasus-kasus tertentu dan variabel yang memiliki nilai yang cocok dengan [operator yang seperti (, !, =, -e, dan sebagainya.

Biarkan saya jelaskan alasannya, dan bagaimana cara mengatasinya dalam shell Bash dan POSIX.


Penjelasan:

Pertimbangkan yang berikut ini:

x="("
[ "$x" = "(" ] && echo yes || echo no

Tidak masalah; hasil di atas tidak ada kesalahan, dan output yes. Beginilah cara kami mengharapkan barang bekerja. Anda dapat mengubah string perbandingan menjadi '1'jika Anda suka, dan nilainya x, dan itu akan berfungsi seperti yang diharapkan.

Perhatikan bahwa /usr/bin/[biner yang sebenarnya berperilaku dengan cara yang sama. Jika Anda menjalankan mis. '/usr/bin/[' '(' = '(' ']'Tidak ada kesalahan, karena program dapat mendeteksi bahwa argumen terdiri dari operasi perbandingan string tunggal.

Bug terjadi ketika kita dan dengan ekspresi kedua. Tidak masalah apa ekspresi kedua, asalkan valid. Sebagai contoh,

[ '1' = '1' ] && echo yes || echo no

output yes, dan jelas merupakan ekspresi yang valid; tapi, jika kita gabungkan keduanya,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash menolak ekspresi jika dan hanya jika xada (atau !.

Jika kita menjalankan di atas menggunakan [program yang sebenarnya , yaitu

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

kesalahan akan dapat dimengerti: karena shell melakukan substitusi variabel, /usr/bin/[biner hanya menerima parameter ( = ( -a 1 = 1dan terminating ], itu dimengerti gagal untuk menguraikan apakah tanda kurung buka memulai sub-ekspresi atau tidak, ada dan operasi yang terlibat. Tentu, menguraikannya sebagai dua perbandingan string adalah mungkin, tetapi melakukannya dengan rakus seperti itu dapat menyebabkan masalah ketika diterapkan pada ekspresi yang tepat dengan sub-ekspresi yang diurung.

Masalahnya, sebenarnya, shell [built-in berperilaku dengan cara yang sama, seolah-olah itu memperluas nilai xsebelum memeriksa ekspresi.

(Ambiguitas ini, dan yang lainnya terkait dengan ekspansi variabel, adalah alasan besar mengapa Bash diimplementasikan dan sekarang merekomendasikan penggunaan [[ ... ]]ekspresi tes sebagai gantinya.)


Penanganannya sepele, dan sering terlihat dalam skrip menggunakan shshell yang lebih lama . Anda menambahkan karakter "aman", sering x, di depan string (kedua nilai dibandingkan), untuk memastikan ekspresi diakui sebagai perbandingan string:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Hewan Nominal
sumber
1
Saya tidak akan menyebut perilaku [bug bawaan. Jika ada, itu adalah cacat desain yang melekat. [[adalah kata kunci shell , bukan hanya perintah bawaan, sehingga ia dapat melihat hal-hal sebelum penghapusan kutipan, dan benar-benar menimpa pemisahan kata yang biasa. mis. [[ $x == 1 ]]tidak perlu menggunakan "$x", karena [[konteksnya berbeda dari normal. Lagi pula, ini adalah bagaimana [[bisa menghindari jebakan [. POSIX perlu [berperilaku seperti itu, dan bash sebagian besar adalah POSIX compliant bahkan tanpa --posix, jadi mengubah [menjadi kata kunci tidak menarik.
Peter Cordes
Solusi Anda tidak disarankan oleh POSIX. Cukup gunakan dua panggilan untuk [.
Wildcard
@PeterCordes: Cukup benar; poin yang bagus. (Saya mungkin seharusnya menggunakan desain daripada didefinisikan dalam paragraf pertama saya, tetapi [perilaku yang dibebani oleh sejarah sebelum POSIX, saya memilih kata yang terakhir sebagai gantinya.) Saya pribadi menghindari menggunakan [[dalam contoh skrip saya, tetapi hanya karena itu adalah pengecualian terhadap rekomendasi mengutip yang selalu saya bahas (karena menghilangkan kutipan adalah alasan paling umum untuk bug skrip yang saya lihat), dan saya belum memikirkan paragraf sederhana untuk menjelaskan mengapa [[pengecualian pada aturan, tanpa membuat rekomendasi kutipan saya tersangka.
Hewan Nominal
@ Kartu Memori: Tidak. POSIX tidak merekomendasikan praktik ini , dan itulah yang penting. Hanya karena otoritas tidak kebetulan merekomendasikan praktik ini, tidak membuat ini buruk. Memang, seperti yang saya tunjukkan, ini adalah praktik historis yang digunakan dalam shskrip, mendahului standardisasi POSIX. Menggunakan subekspresi tanda kurung dan -adan -ooperator logis dalam pengujian / [lebih efisien yang bergantung pada perangkaian ekspresi (via &&dan ||); hanya saja pada mesin saat ini, perbedaannya tidak relevan.
Nominal Animal
@ Kartu Memori: Namun, saya secara pribadi lebih suka untuk menggunakan ekspresi tes &&dan menggunakan ||, tetapi alasannya, itu membuat kita manusia lebih mudah untuk memelihara (membaca, memahami, dan memodifikasi jika / bila perlu) mereka tanpa memperkenalkan bug. Jadi, saya tidak mengkritik saran Anda, tetapi hanya alasan di balik saran itu. (Untuk alasan yang sangat mirip, yang .LT., .GE., dll operator perbandingan dalam FORTRAN 77 mendapat versi yang lebih ramah manusia <, >=dll di versi.)
Nominal Hewan
11

[alias testmelihat:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testmenerima subekspresi dalam tanda kurung; jadi ia berpikir bahwa kurung kurawal membuka subekspresi dan mencoba menguraikannya; pengurai melihat =sebagai hal pertama dalam subekspresi dan berpikir bahwa itu adalah tes panjang-string implisit, jadi itu menyenangkan; subexpression yang kemudian harus diikuti dengan kurung kanan, dan sebaliknya parser menemukan 1bukan ). Dan itu mengeluh.

Ketika testmemiliki tepat tiga argumen, dan argumen tengah adalah salah satu operator yang diakui, itu berlaku operator untuk argumen 1 dan 3 tanpa mencari subekspresi dalam tanda kurung.

Untuk rincian lengkap melihat man bash, mencari test expr.

Kesimpulan: Algoritma parsing yang digunakan oleh testrumit. Gunakan hanya ekspresi sederhana dan gunakan operator shell! , &&dan ||untuk menggabungkannya.

AlexP
sumber