Kapan harus membungkus kutipan di sekitar variabel shell?

184

Bisakah seseorang memberi tahu saya apakah saya harus membungkus tanda kutip di sekitar variabel dalam skrip shell?

Sebagai contoh, apakah yang berikut ini benar:

xdg-open $URL 
[ $? -eq 2 ]

atau

xdg-open "$URL"
[ "$?" -eq "2" ]

Dan jika demikian, mengapa?

Cristian
sumber
Pertanyaan ini mendapat banyak duplikat, banyak di antaranya bukan tentang variabel, jadi saya diberi judul "nilai", bukan "variabel". Saya harap ini membantu lebih banyak orang menemukan topik ini.
tripleee
1
@codeforester Ada apa dengan hasil edit yang dikembalikan?
tripleee

Jawaban:

131

Aturan umum: kutip jika itu bisa kosong atau mengandung spasi (atau spasi kosong apa pun) atau karakter khusus (wildcard). Tidak mengutip string dengan spasi sering menyebabkan shell memecah satu argumen menjadi banyak.

$?tidak perlu penawaran karena ini adalah nilai numerik. Apakah $URLkebutuhan itu tergantung pada apa yang Anda izinkan di sana dan apakah Anda masih menginginkan argumen jika kosong.

Saya cenderung selalu mengutip string hanya karena kebiasaan karena lebih aman seperti itu.

paxdiablo
sumber
2
Perhatikan bahwa "spasi" benar-benar berarti "spasi kosong".
William Pursell
4
@ Kristen: Jika Anda tidak yakin apa yang mungkin ada dalam variabel, lebih aman untuk mengutipnya. Saya cenderung mengikuti prinsip yang sama dengan paxdiablo, dan biasakan mengutip semuanya (kecuali ada alasan khusus untuk tidak melakukannya).
Gordon Davisson
11
Jika Anda tidak tahu nilai IFS, kutip apa pun yang terjadi. Jika IFS=0, maka echo $?bisa sangat mengejutkan.
Charles Duffy
3
Mengutip berdasarkan konteks, bukan pada apa yang Anda harapkan nilainya, jika tidak, bug Anda akan lebih buruk. Misalnya, Anda yakin tidak ada jalur yang memiliki ruang, jadi Anda pikir Anda bisa menulis cp $source1 $source2 $dest, tetapi jika karena alasan yang desttidak terduga tidak ditetapkan, argumen ketiga menghilang, dan itu akan secara diam-diam menyalin source1alih- source2alih memberi Anda kesalahan yang sesuai untuk tujuan kosong (seperti yang akan terjadi jika Anda telah mengutip setiap argumen).
Derek Veit
3
quote it if...memiliki proses berpikir mundur - kutipan bukanlah sesuatu yang Anda tambahkan saat Anda perlu, itu sesuatu yang Anda hapus saat Anda perlu. Selalu bungkus string dan skrip dalam tanda kutip tunggal kecuali jika Anda perlu menggunakan tanda kutip ganda (misalnya untuk membiarkan variabel berkembang) atau perlu menggunakan tanda kutip tidak ada (misalnya untuk melakukan globbing dan ekspansi nama file).
Ed Morton
92

Singkatnya, kutip semua hal di mana Anda tidak memerlukan shell untuk melakukan ekspansi token dan ekspansi wildcard.

Kutipan tunggal melindungi teks di antara mereka kata demi kata. Ini adalah alat yang tepat ketika Anda perlu memastikan bahwa shell tidak menyentuh tali sama sekali. Biasanya, ini adalah mekanisme pilihan mengutip ketika Anda tidak memerlukan interpolasi variabel.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Kutipan ganda cocok ketika interpolasi variabel diperlukan. Dengan adaptasi yang sesuai, ini juga merupakan solusi yang baik ketika Anda membutuhkan tanda kutip tunggal dalam string. (Tidak ada cara langsung untuk melepaskan diri dari kutipan tunggal antara kutipan tunggal, karena tidak ada mekanisme melarikan diri di dalam kutipan tunggal - jika ada, mereka tidak akan mengutip sepenuhnya kata demi kata.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Tidak ada kutipan yang cocok ketika Anda secara khusus meminta shell untuk melakukan pemisahan token dan / atau ekspansi wildcard.

Pemisahan Token;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Sebaliknya:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Loop hanya berjalan sekali, di atas string tunggal, yang dikutip.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Loop hanya berjalan sekali, di atas string tunggal yang dikutip.)

Ekspansi wildcard:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Sebaliknya:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Tidak ada file bernama harfiah file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Tidak ada file bernama $pattern, baik!)

Dalam istilah yang lebih konkret, apa pun yang mengandung nama file biasanya harus dikutip (karena nama file dapat berisi spasi putih dan karakter meta shell lainnya). Apa pun yang mengandung URL biasanya dikutip (karena banyak URL berisi karakter meta shell seperti ?dan &). Apa pun yang mengandung suatu regex biasanya harus dikutip (ditto ditto). Apa pun yang mengandung spasi putih signifikan selain spasi tunggal antara karakter non-spasi putih perlu dikutip (karena jika tidak, shell akan memasukkan ruang putih menjadi, secara efektif, spasi tunggal, dan memangkas spasi spasi apa pun yang mengarah atau tertinggal).

Ketika Anda tahu bahwa suatu variabel hanya dapat berisi nilai yang tidak mengandung karakter meta shell, mengutip adalah opsional. Jadi, tanda kutip $?pada dasarnya baik-baik saja, karena variabel ini hanya dapat berisi satu angka. Namun, "$?"ini juga benar, dan direkomendasikan untuk konsistensi dan kebenaran umum (meskipun ini adalah rekomendasi pribadi saya, bukan kebijakan yang diakui secara luas).

Nilai-nilai yang bukan variabel pada dasarnya mengikuti aturan yang sama, meskipun Anda kemudian bisa juga melarikan diri dari setiap metakarakter alih-alih mengutipnya. Sebagai contoh umum, URL dengan &di dalamnya akan diurai oleh shell sebagai perintah latar belakang kecuali metacharacter lolos atau dikutip:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Tentu saja, ini juga terjadi jika URL berada dalam variabel yang tidak dikutip.) Untuk string statis, tanda kutip tunggal paling masuk akal, meskipun segala bentuk kutip atau melarikan diri berfungsi di sini.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

Contoh terakhir juga menyarankan konsep lain yang bermanfaat, yang saya suka menyebutnya "mengutip jungkat-jungkit". Jika Anda perlu mencampur tanda kutip tunggal dan ganda, Anda dapat menggunakannya berdekatan. Misalnya, string yang dikutip berikut

'$HOME '
"isn't"
' where `<3'
"' is."

dapat disisipkan bersama kembali ke belakang, membentuk string panjang tunggal setelah tokenization dan penghapusan kutipan.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Ini tidak terbaca dengan baik, tetapi ini adalah teknik yang umum dan karenanya baik untuk diketahui.

Selain itu, skrip biasanya tidak boleh digunakan lsuntuk apa pun. Untuk memperluas wildcard, cukup ... gunakan saja.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(Pengulangan benar-benar berlebihan dalam contoh terakhir; printfsecara khusus berfungsi dengan baik dengan beberapa argumen stat. Tetapi pengulangan pada pencocokan wildcard adalah masalah umum, dan sering dilakukan secara tidak benar.)

Variabel yang berisi daftar token untuk diulang atau wildcard untuk diekspansi lebih jarang terlihat, jadi kami terkadang menyingkat menjadi "mengutip segalanya kecuali Anda tahu persis apa yang Anda lakukan".

tripleee
sumber
1
Ini adalah varian (bagian dari) jawaban yang saya poskan ke pertanyaan terkait . Saya menempelkannya di sini karena ini singkat dan cukup jelas untuk menjadi pertanyaan kanonik untuk masalah khusus ini.
tripleee
4
Saya akan mencatat bahwa ini adalah item # 0 dan tema yang berulang pada koleksi mywiki.wooledge.org/BashPitfalls dari kesalahan Bash yang umum. Banyak, banyak item individual pada daftar itu pada dasarnya tentang masalah ini.
tripleee
27

Berikut adalah rumus tiga poin untuk kutipan secara umum:

Kutipan ganda

Dalam konteks di mana kami ingin menekan pemisahan kata dan penggumpalan. Juga dalam konteks di mana kita ingin literal diperlakukan sebagai string, bukan regex.

Kutipan tunggal

Dalam string literal di mana kami ingin menekan interpolasi dan perlakuan khusus backslash. Dengan kata lain, situasi di mana menggunakan tanda kutip ganda tidak pantas.

Tidak ada kutipan

Dalam konteks di mana kita benar-benar yakin bahwa tidak ada masalah pemisahan kata atau penggumpalan atau kita memang ingin pemisahan kata dan penggumpalan .


Contohnya

Kutipan ganda

  • string literal dengan spasi putih ( "StackOverflow rocks!", "Steve's Apple")
  • ekspansi variabel ( "$var", "${arr[@]}")
  • substitusi perintah ( "$(ls)", "`ls`")
  • gumpalan di mana jalur direktori atau bagian nama file menyertakan spasi ( "/my dir/"*)
  • untuk melindungi tanda kutip tunggal ( "single'quote'delimited'string")
  • Perluasan parameter bash ( "${filename##*/}")

Kutipan tunggal

  • perintah nama dan argumen yang memiliki spasi putih di dalamnya
  • string literal yang membutuhkan interpolasi untuk ditekan ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • untuk melindungi tanda kutip ganda ( 'The "crux"')
  • regex literal yang membutuhkan interpolasi harus ditekan
  • gunakan kutipan shell untuk literal yang melibatkan karakter khusus ( $'\n\t')
  • gunakan kutipan shell di mana kita perlu melindungi beberapa tanda kutip tunggal dan ganda ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Tidak ada kutipan

  • variabel numerik sekitar standar ( $$, $?, $#dll)
  • dalam konteks aritmatika seperti ((count++)), "${arr[idx]}","${string:start:length}"
  • [[ ]]ekspresi di dalam yang bebas dari masalah pemisahan kata dan penggumpalan (ini masalah gaya dan pendapat bisa sangat bervariasi)
  • tempat kami ingin pemisahan kata ( for word in $words)
  • tempat kami ingin globbing ( for txtfile in *.txt; do ...)
  • di mana kita ingin ~ditafsirkan sebagai $HOME( ~/"some dir"tetapi tidak "~/some dir")

Lihat juga:

codeforester
sumber
3
Menurut pedoman ini, orang akan mendapatkan daftar file di direktori root dengan menulis "ls" "/" Frasa "semua konteks string" harus dikualifikasi lebih hati-hati.
William Pursell
5
Dalam [[ ]], mengutip tidak masalah di sisi kanan =/ ==dan =~: itu membuat perbedaan antara menafsirkan string sebagai pola / regex atau secara harfiah.
Benjamin W.
6
Tinjauan yang baik, tetapi komentar @ BenjaminW. Layak untuk diintegrasikan dan string ANSI C-dikutip ( $'...') harus memiliki bagian mereka sendiri.
mklement0
3
@ mklement0, memang mereka setara. Panduan ini menunjukkan bahwa Anda harus selalu mengetik "ls" "/"daripada yang lebih umum ls /, dan saya menganggap itu sebagai cacat utama dalam pedoman.
William Pursell
4
Untuk tanpa tanda kutip Anda bisa menambahkan variabel tugas atau case:)
PesaThe
4

Saya biasanya menggunakan dikutip seperti "$var"untuk aman, kecuali saya yakin itu $vartidak mengandung ruang.

Saya menggunakan $varcara sederhana untuk bergabung dengan baris:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
Bach Lien
sumber
Komentar terakhir agak menyesatkan; baris baru secara efektif diganti dengan spasi, tidak hanya dihapus.
tripleee
-1

Untuk menggunakan variabel dalam skrip shell gunakan "" variabel yang dikutip seperti yang dikutip berarti bahwa variabel tersebut mungkin berisi spasi atau karakter khusus yang tidak akan mempengaruhi eksekusi skrip shell Anda. Jika Anda yakin tidak memiliki spasi atau karakter khusus dalam nama variabel Anda, maka Anda dapat menggunakannya tanpa "".

Contoh:

echo "$ nama url" - (Dapat digunakan setiap saat)

echo "$ url name" - (Tidak dapat digunakan pada situasi seperti itu jadi berhati-hatilah sebelum menggunakannya)

Vipul Sharma
sumber