Ekspansi variabel di dalam tanda kutip tunggal dalam perintah di Bash

393

Saya ingin menjalankan perintah dari skrip bash yang memiliki tanda kutip tunggal dan beberapa perintah lain di dalam tanda kutip tunggal dan variabel.

misalnya repo forall -c '....$variable'

Dalam format ini, $lolos dan variabel tidak diperluas.

Saya mencoba variasi berikut tetapi ditolak:

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

Jika saya mengganti nilai di tempat variabel perintah dieksekusi dengan baik.

Tolong beritahu saya di mana saya salah.

Rachit
sumber
33
repo forall -c ' ...before... '"$variable"' ...after...'
n. 'kata ganti' m.
2
@nm tanda kutip tunggal adalah bagian dari perintah repo. Saya rasa ini tidak akan berhasil
Rachit
1
bashmakan tanda kutip tunggal. Entah Anda tidak masuk bash, atau tanda kutip tunggal bukan bagian dari repoperintah.
n. 'kata ganti' m.
Tidak yakin saya mendapatkan Anda, tetapi jika ada tanda kutip perlu ada di sana, tidak akan repo forall -c "'... before ..." $ variable "... after ...'" work?
ecv
Script bash-nya berjalan di bash shell dan perintah repo forall mengambil parameter di dalam tanda kutip tunggal. Jika saya mengganti nilai aktual, perintah berjalan dengan baik.
Rachit

Jawaban:

621

Di dalam kutipan tunggal semuanya dilestarikan secara harfiah, tanpa kecuali.

Itu berarti Anda harus menutup tanda kutip, memasukkan sesuatu, dan kemudian memasukkan kembali.

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

Penggabungan kata hanya dilakukan dengan penjajaran. Seperti yang dapat Anda verifikasi, setiap baris di atas adalah satu kata untuk shell. Kutipan (kutipan tunggal atau ganda, tergantung situasi) tidak mengisolasi kata-kata. Mereka hanya digunakan untuk menonaktifkan interpretasi berbagai karakter khusus, seperti spasi,$ ,, ;... Untuk tutorial yang baik tentang kutipan, lihat jawaban Mark Reed. Juga relevan: Karakter mana yang harus dihindari dalam bash?

Jangan menyatukan string yang ditafsirkan oleh shell

Anda harus benar-benar menghindari membuat perintah shell dengan menggabungkan variabel. Ini adalah ide buruk yang mirip dengan rangkaian fragmen SQL (injeksi SQL!).

Biasanya dimungkinkan untuk memiliki placeholder dalam perintah, dan untuk memasok perintah bersama dengan variabel sehingga callee dapat menerimanya dari daftar argumen doa.

Misalnya, berikut ini sangat tidak aman. JANGAN LAKUKAN INI

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

Jika konten $myvartidak dipercaya, berikut ini adalah exploit:

myvar='foo"; echo "you were hacked'

Alih-alih doa di atas, gunakan argumen posisi. Doa berikut lebih baik - tidak bisa dieksploitasi:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"

Catat penggunaan kutu tunggal dalam penugasan script, yang artinya diambil secara harfiah, tanpa ekspansi variabel atau bentuk interpretasi lainnya.

Jo So
sumber
@ Jo.Jadi itu tidak akan berhasil. karena perintah repo hanya akan mengambil parameter sampai tanda kutip pertama ditutup. Apa pun setelah itu akan diabaikan untuk repo dan menghasilkan kesalahan lain.
Rachit
8
@Rachit - shell tidak berfungsi seperti itu. "some"thing' like'thisadalah satu kata untuk shell. Tanda kutip tidak menghentikan apa pun sejauh menyangkut penguraian, dan tidak ada cara bagi perintah yang dipanggil oleh shell untuk memberi tahu apa yang dikutip bagaimana caranya.
Mark Reed
3
Kalimat kedua itu ("Anda bahkan tidak bisa lolos dari tanda kutip tunggal") membuat tanda kutip tunggal lubang hitam Shell.
@ Balik: Tidak tahu bagaimana mengatakannya dengan lebih baik. Saya sudah menghapus kalimatnya.
Jo So
@ JoSo Itu memalukan: tidak ada lelucon yang datar.
96

The repoperintah tidak bisa peduli apa jenis kutipan mendapat. Jika Anda membutuhkan ekspansi parameter, gunakan tanda kutip ganda. Jika itu berarti Anda akhirnya harus melakukan backslash banyak hal, gunakan tanda kutip tunggal untuk sebagian besar, dan kemudian keluar dari mereka dan pergi ke ganda untuk bagian di mana Anda perlu ekspansi terjadi.

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

Penjelasan berikut, jika Anda tertarik.

Saat Anda menjalankan perintah dari shell, perintah yang diterima sebagai argumen adalah array dari string yang diakhiri dengan null. String-string itu mungkin benar - benar mengandung karakter bukan nol.

Tetapi ketika shell sedang membangun array string dari baris perintah, itu menafsirkan beberapa karakter khusus; ini dirancang untuk membuat perintah lebih mudah (memang, mungkin) untuk diketik. Misalnya, spasi biasanya menunjukkan batas antara string dalam array; untuk alasan itu, argumen individu kadang-kadang disebut "kata-kata". Namun suatu argumen mungkin memiliki ruang di dalamnya; Anda hanya perlu beberapa cara untuk memberi tahu shell itu yang Anda inginkan.

Anda dapat menggunakan garis miring terbalik di depan karakter apa pun (termasuk spasi, atau garis miring terbalik lainnya) untuk memberi tahu shell untuk memperlakukan karakter tersebut secara harfiah. Tetapi sementara Anda dapat melakukan sesuatu seperti ini:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

... itu bisa melelahkan. Jadi cangkang menawarkan alternatif: tanda kutip. Ini datang dalam dua varietas utama.

Tanda kutip ganda disebut "kutipan pengelompokan". Mereka mencegah wildcard dan alias diperluas, tetapi sebagian besar mereka untuk memasukkan spasi dalam kata. Hal-hal lain seperti perluasan parameter dan perintah (hal-hal yang ditandai oleh a $) masih terjadi. Dan tentu saja jika Anda ingin tanda kutip ganda di dalam tanda kutip ganda, Anda harus melakukan backslash:

echo "\"Thank you. That'll be \$4.96, please,\" said the cashier"

Tanda kutip tunggal lebih kejam. Segala sesuatu di antara mereka diambil sepenuhnya secara harfiah, termasuk garis miring terbalik. Sama sekali tidak ada cara untuk mendapatkan kutipan tunggal literal di dalam kutipan tunggal.

Untungnya, tanda kutip di shell bukan pembatas kata ; sendiri, mereka tidak mengakhiri sepatah kata pun. Anda bisa masuk dan keluar dari kutipan, termasuk antara berbagai jenis kutipan, dalam kata yang sama untuk mendapatkan hasil yang diinginkan:

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

Jadi itu lebih mudah - backslash jauh lebih sedikit, meskipun urutan kutipan-tertutup, backslashed-literal-kutipan-tunggal, buka-kutipan-tunggal memerlukan waktu untuk membiasakan diri.

Kerang modern telah menambahkan gaya kutipan lain yang tidak ditentukan oleh standar POSIX, di mana tanda kutip tunggal yang diawali diawali dengan tanda dolar. String yang dikutip mengikuti konvensi serupa ke string literal dalam bahasa pemrograman ANSI C, dan karena itu kadang-kadang disebut "string ANSI" dan $'... 'pasangan "kutipan ANSI". Dalam string seperti itu, saran di atas tentang backslash yang diambil secara harfiah tidak lagi berlaku. Sebaliknya, mereka menjadi istimewa lagi - Anda tidak hanya dapat menyertakan tanda kutip tunggal literal atau garis miring terbalik dengan menambahkan garis miring terbalik padanya, tetapi shell juga memperluas karakter ANSI C yang lolos (seperti \nuntuk baris baru,\t untuk tab, dan \xHHuntuk karakter dengan kode heksadesimalHH). Namun, jika tidak, mereka berperilaku sebagai string yang dikutip tunggal: tidak ada parameter atau substitusi perintah yang terjadi:

echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

Yang penting untuk dicatat adalah bahwa string tunggal yang diterima sebagai argumen ke echoperintah persis sama dalam semua contoh ini. Setelah shell selesai mengurai baris perintah, tidak ada cara untuk menjalankan perintah untuk mengetahui apa yang dikutip bagaimana. Bahkan jika itu mau.

Mark Reed
sumber
2
Iya. Saya mengerti sekarang. Ini bekerja dengan sempurna. Terima kasih banyak atas bantuan Anda!
Rachit
6
Balasan ini luar biasa.
Vinícius Ferrão
1
Apakah $'string'format POSIX sesuai? Juga, apakah ada nama untuk itu?
Wildcard
2
@Wildcard $'string'adalah ekstensi non-POSIX, yang saya lihat disebut sebagai "string ANSI"; Saya telah memasukkan fakta-fakta itu ke dalam jawabannya. String semacam itu berfungsi di sebagian besar shell kompatibel-Bourne modern: bash, dash, ksh (baik AT&T dan PD), dan zsh semua mendukungnya.
Mark Reed
1
Tautan balik - Karakter apa yang diperlukan untuk lolos dalam argumen baris perintah? di pertukaran tumpukan Unix & Linux.
Wildcard
3

Di bawah ini adalah yang bekerja untuk saya -

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"
Manmohan
sumber
2
Saya harap TBL_HDFS_DIR_PATH bukan input yang diberikan pengguna btw, ini akan memungkinkan injeksi sql yang bagus ...
domenukk
1
Menempatkan QUOTEdalam variabel yang terpisah benar-benar berlebihan; Kutipan tunggal di dalam tanda kutip ganda hanyalah karakter biasa. (Juga, jangan gunakan huruf besar untuk variabel pribadi Anda.)
tripleee
2

EDIT: (Sesuai komentar yang diajukan :)

Saya sudah melihat ini sejak itu. Saya cukup beruntung bahwa saya punya repo berbaring. Masih tidak jelas bagi saya apakah Anda harus menyertakan perintah Anda di antara tanda kutip tunggal dengan paksa. Saya melihat sintaks repo dan saya pikir Anda tidak perlu melakukannya. Anda bisa menggunakan tanda kutip ganda di sekitar perintah Anda, dan kemudian menggunakan tanda kutip tunggal dan ganda apa pun yang Anda butuhkan di dalam asalkan Anda lolos dari tanda kutip ganda.

ecv
sumber
Terima kasih Kevin atas bantuan Anda.
Rachit
2

cukup gunakan printf

dari pada

repo forall -c '....$variable'

gunakan printf untuk mengganti token variabel dengan variabel yang diperluas.

Sebagai contoh:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")
AndrewD
sumber
Ini rusak; printftidak benar-benar membelikan Anda apa pun di sini, dan gagal mengutip perintah subsititusi menyebabkan masalah kutipan baru.
tripleee
0

Variabel dapat berisi tanda kutip tunggal.

myvar=\'....$variable\'

repo forall -c $myvar
pengguna3734617
sumber
Mereka bisa, tetapi ini bukan cara yang tepat untuk melakukannya - Anda masih harus memberi "$myvar"tanda kutip ganda.
tripleee
-2

Apakah ini Bekerja untukmu?

eval repo forall -c '....$variable'
drgnfr
sumber
8
Apakah itu untukmu? Pertanyaan ini berusia lima tahun dan sudah memiliki beberapa jawaban, bahkan jawaban yang diterima ...
Nico Haase