Mengapa ekspansi parameter dengan spasi tanpa tanda kutip bekerja di dalam tanda kurung ganda "[[" tetapi tidak di dalam tanda kurung tunggal "["?

86

Saya bingung dengan menggunakan tanda kurung tunggal atau ganda. Lihatlah kode ini:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Ini berfungsi dengan baik meskipun string berisi spasi. Tetapi ketika saya mengubahnya ke braket tunggal:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Ia mengatakan:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Ketika saya mengubahnya ke:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Ini bekerja dengan baik. Adakah yang bisa menjelaskan apa yang terjadi? Kapan saya harus memberikan tanda kutip ganda di sekitar variabel "${var}"untuk mencegah masalah yang disebabkan oleh spasi?

Majid Azimi
sumber
8
Bash FAQ 31
jw013
Pertanyaan yang lebih umum vs. pertanyaan: unix.stackexchange.com/questions/306111/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:

83

Braket tunggal [sebenarnya adalah alias untuk testperintah, itu bukan sintaks.

Salah satu kelemahan (dari banyak) braket tunggal adalah bahwa jika satu atau lebih operan yang mencoba mengevaluasi mengembalikan string kosong, ia akan mengeluh bahwa ia mengharapkan dua operan (biner). Inilah sebabnya mengapa Anda melihat orang-orang melakukannya [ x$foo = x$blah ], xjaminan bahwa operan tidak akan pernah mengevaluasi ke string kosong.

Braket ganda [[ ]], di sisi lain, adalah sintaks dan jauh lebih mampu daripada [ ]. Seperti yang Anda ketahui, ia tidak memiliki masalah operan tunggal dan juga memungkinkan sintaksis mirip C dengan >, <, >=, <=, !=, ==, &&, ||operator.

Rekomendasi saya adalah sebagai berikut: Jika penerjemah Anda adalah #!/bin/bash, maka selalu gunakan[[ ]]

Penting untuk dicatat bahwa [[ ]]tidak didukung oleh semua shell POSIX, namun banyak shell yang mendukungnya seperti zshdan kshselainbash

SiegeX
sumber
16
Masalah string kosong (dan banyak lainnya) diselesaikan dengan menggunakan tanda kutip. "X" adalah untuk mengatasi jenis masalah lain: di mana operan dapat diambil sebagai operator . Seperti saat $fooini !atau (atau -n... Masalah itu tidak dimaksudkan untuk menjadi masalah dengan kerang POSIX mana jumlah argumen (samping [dan ]) tidak lebih besar dari empat.
Stéphane Chazelas
21
Untuk memperjelas, [ x$foo = x$blah ]sama salahnya dengan [ $foo = $bar ]. [ "$foo" = "$bar" ]benar di semua shell yang sesuai dengan POSIX, [ "x$foo" = "x$bar" ]akan berfungsi di shell seperti Bourne, tetapi xtidak untuk kasus di mana Anda memiliki string kosong tetapi di mana $foomungkin !atau -n...
Stéphane Chazelas
1
Semantik yang menarik dalam jawaban ini. Saya tidak yakin apa yang Anda maksud dengan sintaks * bukan * itu . Tentu saja, [bukan alias untuk test. Jika ya, maka testakan menerima tanda kurung siku. test -n foo ]. Tetapi testtidak, dan [membutuhkan satu. [identik dengan testdalam semua hal lain, tapi bukan itu cara aliaskerjanya. Bash mendeskripsikan [dan testsebagai builtin shell , tetapi [[sebagai kata kunci shell .
kojiro
1
Nah, di bash [shell adalah builtin, tetapi / usr / bin / [juga bisa dieksekusi. Ini secara tradisional merupakan tautan ke / usr / bin / test, tetapi di modern gnu coreutils adalah biner yang terpisah. Versi tradisional tes menguji argv [0] untuk melihat apakah itu dipanggil sebagai [dan kemudian mencari yang cocok].
Evan
Pesta saya tidak bekerja dengan >=dan<=
Siswa
52

The [perintah perintah biasa. Meskipun sebagian besar shell menyediakannya sebagai built-in untuk efisiensi, shell mematuhi aturan sintaksis normal shell. [persis sama dengan test, kecuali yang [membutuhkan ]argumen terakhir dan testtidak.

Kurung ganda [[ … ]]adalah sintaks khusus. Mereka diperkenalkan di ksh (beberapa tahun setelah [) karena [dapat merepotkan untuk digunakan dengan benar dan [[memungkinkan beberapa tambahan bagus baru yang menggunakan karakter khusus shell. Misalnya, Anda bisa menulis

[[ $x = foo && $y = bar ]]

karena seluruh ekspresi kondisional diuraikan oleh shell, sedangkan [ $x = foo && $y = bar ]pertama-tama akan dibagi menjadi dua perintah [ $x = foodan $y = bar ]dipisahkan oleh &&operator. Demikian pula tanda kurung ganda memungkinkan hal-hal seperti sintaksis pencocokan pola, misalnya [[ $x == a* ]]untuk menguji apakah nilai xdiawali dengan a; dalam kurung tunggal ini akan diperluas a*ke daftar file yang namanya dimulai dengan adi direktori saat ini. Kurung ganda pertama kali diperkenalkan dalam ksh dan hanya tersedia dalam ksh, bash, dan zsh.

Di dalam tanda kurung tunggal, Anda perlu menggunakan tanda kutip ganda di sekitar substitusi variabel, seperti di sebagian besar tempat lain, karena mereka hanya argumen untuk perintah (yang kebetulan adalah [perintah). Di dalam tanda kurung ganda, Anda tidak perlu tanda kutip ganda, karena shell tidak melakukan pemisahan kata atau globbing: ia menguraikan ekspresi kondisional, bukan perintah.

Pengecualian adalah di [[ $var1 = "$var2" ]]mana Anda membutuhkan tanda kutip jika Anda ingin melakukan perbandingan string byte-ke-byte, jika tidak, $var2akan menjadi pola yang cocok $var1.

Satu hal yang tidak dapat Anda lakukan [[ … ]]adalah menggunakan variabel sebagai operator. Misalnya, ini legal (tetapi jarang bermanfaat):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

Dalam contoh Anda

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

perintah dalam ifadalah [dengan 4 argumen -d, /home/mazimi/VirtualBox, VMsdan ]. Shell mengurai -d /home/mazimi/VirtualBoxdan kemudian tidak tahu apa yang harus dilakukan VMs. Anda perlu mencegah pemisahan kata ${dir}untuk mendapatkan perintah yang dibuat dengan baik.

Secara umum, selalu gunakan tanda kutip ganda di sekitar penggantian variabel dan perintah kecuali jika Anda tahu Anda ingin melakukan pemisahan kata dan globbing pada hasilnya. Tempat-tempat utama di mana aman untuk tidak menggunakan tanda kutip ganda adalah:

  • dalam penugasan: foo=$bar(tetapi perhatikan bahwa Anda membutuhkan tanda kutip ganda dalam export "foo=$bar"atau dalam penugasan array seperti array=("$a" "$b"));
  • dalam sebuah casepernyataan case $foo in …:;
  • dalam kurung ganda kecuali di sisi kanan =atau ==operator (kecuali jika Anda melakukan ingin pencocokan pola): [[ $x = "$y" ]].

Dalam semua ini, itu benar untuk menggunakan tanda kutip ganda, jadi Anda mungkin juga melewatkan aturan lanjutan dan menggunakan tanda kutip sepanjang waktu.

Gilles
sumber
1
@StephaneChazelas Kesalahan saya. Ternyata [memang dari Sistem III pada tahun 1981 , saya pikir itu lebih tua.
Gilles
8

Kapan saya harus memberikan tanda kutip ganda di sekitar variabel "${var}"untuk mencegah masalah yang disebabkan oleh spasi?

Tersirat dalam pertanyaan ini adalah

Mengapa tidak cukup baik?${variable_name}

${variable_name} tidak berarti apa yang Anda pikirkan ...

... jika Anda pikir itu ada hubungannya dengan masalah yang disebabkan oleh spasi (dalam nilai variabel). baik untuk ini:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

dan tidak ada lagi! 1  tidak ada gunanya kecuali Anda segera mengikutinya dengan karakter yang bisa menjadi bagian dari nama variabel: huruf (-atau-), garis bawah (), atau digit (-). Dan bahkan kemudian, Anda dapat mengatasinya:${variable_name}AZaz_09

$ echo "$bar"d
food

Saya tidak mencoba untuk mencegah penggunaannya - echo "${bar}d"mungkin merupakan solusi terbaik di sini - tetapi untuk mencegah orang-orang dari bergantung pada kawat gigi daripada tanda kutip, atau menerapkan kawat gigi secara naluriah dan kemudian bertanya, "Sekarang, apakah saya perlu tanda kutip, juga? ”  Anda harus selalu menggunakan tanda kutip kecuali Anda memiliki alasan kuat untuk tidak melakukannya, dan Anda yakin tahu apa yang Anda lakukan.
_________________
1    Kecuali, tentu saja, untuk fakta bahwa bentuk perluasan parameter yang lebih bagus , misalnya, dan , dibangun di atas sintaks. Selain itu, Anda perlu menggunakan , dll., Untuk referensi parameter posisi 10, 11, dll. - kutipan tidak akan membantu Anda.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}

G-Man
sumber
2

Untuk menangani spasi dan karakter | kosong khusus dalam variabel, Anda harus selalu melingkupinya dengan tanda kutip ganda. Mengatur IFS yang tepat juga merupakan praktik yang baik.

Dianjurkan: http://www.dwheeler.com/essays/filenames-in-shell.html

ramonovski
sumber