Kapan perlu mengutip ganda?

120

Saran lama yang digunakan adalah mengutip dua kali ekspresi yang melibatkan $VARIABLE, setidaknya jika seseorang ingin ditafsirkan oleh shell sebagai satu item tunggal, jika tidak, setiap ruang dalam konten $VARIABLEakan membuang shell.

Saya mengerti, bagaimanapun, bahwa dalam versi shell yang lebih baru, kutip ganda tidak lagi selalu diperlukan (setidaknya untuk tujuan yang dijelaskan di atas). Misalnya, di bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

Di zsh, di sisi lain, tiga perintah yang sama berhasil. Oleh karena itu, berdasarkan percobaan ini, tampaknya, di bash, seseorang dapat menghilangkan tanda kutip ganda di dalam [[ ... ]], tetapi tidak di dalam [ ... ]atau dalam argumen baris perintah, sedangkan, di zsh, tanda kutip ganda dapat dihilangkan dalam semua kasus ini.

Tetapi menyimpulkan aturan umum dari contoh-contoh anekdotal seperti di atas adalah proposisi untung-untungan. Alangkah baiknya untuk melihat ringkasan kapan perlu mengutip ganda. Saya terutama tertarik pada zsh, bash, dan /bin/sh.

kjo
sumber
10
Perilaku Anda yang diamati di zsh tergantung pada pengaturan dan dipengaruhi oleh SH_WORD_SPLITopsi.
Ulrich Dangel
3
Sebagai tambahan - nama variabel all-caps digunakan oleh variabel dengan makna sistem operasi dan shell; spesifikasi POSIX secara eksplisit menyarankan penggunaan nama huruf kecil untuk variabel yang ditentukan aplikasi. (Sementara spesifikasi yang dikutip secara khusus berfokus pada variabel lingkungan, variabel lingkungan dan variabel shell berbagi namespace: Mencoba membuat variabel shell dengan nama yang sudah digunakan oleh variabel lingkungan menimpa yang terakhir). Lihat pubs.opengroup.org/onlinepubs/009695399/basedefs/… , paragraf keempat.
Charles Duffy

Jawaban:

128

Pertama, pisahkan zsh dari yang lain. Ini bukan masalah kerang tua vs modern: zsh berperilaku berbeda. Desainer zsh memutuskan untuk membuatnya tidak kompatibel dengan cangkang tradisional (Bourne, ksh, bash), tetapi lebih mudah digunakan.

Kedua, jauh lebih mudah menggunakan tanda kutip ganda sepanjang waktu daripada mengingat kapan mereka dibutuhkan. Mereka dibutuhkan sebagian besar waktu, jadi Anda harus belajar ketika mereka tidak dibutuhkan, bukan ketika mereka dibutuhkan.

Singkatnya, tanda kutip ganda diperlukan di mana pun daftar kata atau pola diharapkan . Mereka opsional dalam konteks di mana string mentah diharapkan oleh parser.

Apa yang terjadi tanpa tanda kutip

Perhatikan bahwa tanpa tanda kutip ganda, dua hal terjadi.

  1. Pertama, hasil ekspansi (nilai variabel untuk substitusi parameter seperti ${foo}, atau output dari perintah untuk substitusi perintah seperti $(foo)) dibagi menjadi kata-kata di mana pun itu berisi spasi putih.
    Lebih tepatnya, hasil ekspansi dipecah pada setiap karakter yang muncul dalam nilai IFSvariabel (karakter pemisah). Jika urutan karakter pemisah berisi spasi putih (spasi, tab atau baris baru), spasi putih dihitung sebagai karakter tunggal; pemisah non-spasi putih yang dipimpin, tertinggal, atau berulang mengarah ke bidang kosong. Misalnya, dengan IFS=" :", :one::two : three: :four menghasilkan bidang kosong sebelum one, antara onedan two, dan (satu) antara threedan four.
  2. Setiap bidang yang dihasilkan dari pemisahan ditafsirkan sebagai bola (pola wildcard) jika berisi salah satu karakter \[*?. Jika pola itu cocok dengan satu atau beberapa nama file, pola tersebut diganti dengan daftar nama file yang cocok.

Ekspansi variabel yang tidak dikutip $foosecara bahasa dikenal sebagai "operator split + glob", berbeda dengan "$foo"yang hanya mengambil nilai variabel foo. Hal yang sama berlaku untuk substitusi perintah: "$(foo)"adalah substitusi perintah, $(foo)adalah substitusi perintah yang diikuti oleh split + glob.

Di mana Anda dapat menghilangkan tanda kutip ganda

Berikut adalah semua kasus yang dapat saya pikirkan dalam shell Bourne-style di mana Anda dapat menulis variabel atau substitusi perintah tanpa tanda kutip ganda, dan nilainya ditafsirkan secara harfiah.

  • Di sisi kanan penugasan.

    var=$stuff
    a_single_star=*

    Perhatikan bahwa Anda memerlukan tanda kutip ganda setelahnya export, karena ini adalah builtin biasa, bukan kata kunci. Ini hanya berlaku pada beberapa shell seperti dash, zsh (dalam emulasi sh), yash atau posh; bash dan ksh keduanya memperlakukan exportsecara khusus.

    export VAR="$stuff"
  • Dalam sebuah casepernyataan.

    case $var in 

    Perhatikan bahwa Anda perlu tanda kutip ganda dalam pola kasus. Pemisahan kata tidak terjadi dalam pola kasus, tetapi variabel yang tidak dikutip ditafsirkan sebagai pola sedangkan variabel yang dikutip ditafsirkan sebagai string literal.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • Dalam kurung ganda. Kurung ganda adalah sintaks khusus shell.

    [[ -e $filename ]]

    Kecuali Anda memang membutuhkan tanda kutip ganda di mana pola atau ekspresi reguler diharapkan: di sisi kanan =atau ==atau !=atau =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi

    Anda memang perlu tanda kutip ganda seperti biasa dalam kurung tunggal [ … ]karena mereka adalah sintaksis shell biasa (ini adalah perintah yang kebetulan dipanggil [). Lihat kurung tunggal atau ganda

  • Dalam pengalihan di shell POSIX non-interaktif (tidak bash, juga ksh88).

    echo "hello world" >$filename

    Beberapa shell, ketika interaktif, memperlakukan nilai variabel sebagai pola wildcard. POSIX melarang perilaku itu dalam cangkang non-interaktif, tetapi beberapa cangkang termasuk bash (kecuali dalam mode POSIX) dan ksh88 (termasuk ketika ditemukan sebagai (seharusnya) POSIX shdari beberapa Unix komersial seperti Solaris) masih melakukannya di sana ( bashjuga mencoba memisahkan dan pengalihan gagal kecuali bahwa perpecahan + globbing hasil persis satu kata), yang mengapa lebih baik untuk mengutip sasaran pengalihan dalam shnaskah jika anda ingin mengubahnya menjadi bashnaskah beberapa hari, atau menjalankannya pada sistem di mana shadalah tidak sesuai pada titik itu, atau mungkin bersumber dari shell interaktif.

  • Di dalam ekspresi aritmatika. Bahkan, Anda harus meninggalkan tanda kutip agar variabel diuraikan sebagai ekspresi aritmatika.

    expr=2*2
    echo "$(($expr))"

    Namun, Anda memang perlu tanda kutip di sekitar ekspansi aritmatika karena mereka tunduk pada pemisahan kata di sebagian besar kerang sebagai POSIX membutuhkan (!?).

  • Dalam subscript array asosiatif.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

Variabel yang tidak dikutip dan substitusi perintah dapat berguna dalam beberapa situasi yang jarang terjadi:

  • Ketika nilai variabel atau output perintah terdiri dari daftar pola glob dan Anda ingin memperluas pola ini ke daftar file yang cocok.
  • Ketika Anda tahu bahwa nilainya tidak mengandung karakter wildcard, $IFSitu tidak dimodifikasi dan Anda ingin membaginya di karakter spasi.
  • Saat Anda ingin membagi nilai pada karakter tertentu: nonaktifkan globbing with set -f, setel IFSke karakter separator (atau biarkan sendiri untuk dibagi di whitespace), lalu lakukan ekspansi.

Zsh

Di zsh, Anda dapat menghilangkan tanda kutip ganda sebagian besar kali, dengan beberapa pengecualian.

  • $vartidak pernah mengembang ke beberapa kata, namun itu mengembang ke daftar kosong (sebagai lawan dari daftar yang mengandung satu kata, kosong) jika nilainya varadalah string kosong. Kontras:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    Demikian pula, "${array[@]}"memperluas ke semua elemen array, sementara $arrayhanya memperluas ke elemen yang tidak kosong.

  • The @bendera ekspansi parameter kadang-kadang membutuhkan tanda kutip ganda di sekitar seluruh substitusi: "${(@)foo}".

  • Substitusi perintah mengalami pemisahan bidang jika tidak dikutip: echo $(echo 'a'; echo '*')cetakan a *(dengan satu spasi) sedangkan echo "$(echo 'a'; echo '*')"mencetak string dua baris yang tidak dimodifikasi. Gunakan "$(somecommand)"untuk mendapatkan output dari perintah dalam satu kata, tanpa akhir baris baru. Gunakan "${$(somecommand; echo _)%?}"untuk mendapatkan output yang tepat dari perintah termasuk baris baru. Gunakan "${(@f)$(somecommand)}"untuk mendapatkan larik garis dari output perintah.

Gilles
sumber
Bahkan, Anda harus meninggalkan tanda kutip agar variabel diuraikan sebagai ekspresi aritmatika. Mengapa saya dapat membuat contoh Anda berfungsi dengan kutipan:echo "$(("$expr"))"
Cyker
Inilah yang man bashdikatakan: Ekspresi diperlakukan seolah-olah berada dalam tanda kutip ganda, tetapi tanda kutip ganda di dalam tanda kurung tidak diperlakukan secara khusus.
Cyker
4
Juga, bagi siapa saja yang tertarik, nama formal split + glob adalah pemisahan kata dan perluasan pathname .
Cyker
3
FYI - lebih dari pada StackOverflow , saya memiliki seseorang menarik "opsional ketika string mentah diharapkan" bahasa dalam jawaban ini untuk membela tidak mengutip argumen echo. Mungkin ada baiknya mencoba membuat bahasa lebih eksplisit ("ketika string mentah diharapkan oleh parser", mungkin?)
Charles Duffy
2
@ Charles Duffy Ugh, saya belum memikirkan kesalahan baca ini. Saya telah mengubah "di mana" menjadi "kapan" dan memperkuat kalimat seperti yang Anda sarankan.
Gilles