Cara menghindari kutip tunggal dalam satu string yang dikutip

1018

Katakanlah, Anda memiliki Bash aliasseperti:

alias rxvt='urxvt'

yang bekerja dengan baik.

Namun:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

tidak akan bekerja, dan tidak akan:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

Jadi, bagaimana Anda akhirnya mencocokkan pembukaan dan penutupan kutipan di dalam sebuah string setelah Anda lolos dari penawaran?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

tampaknya tidak sopan meskipun itu akan mewakili string yang sama jika Anda diizinkan untuk menyatukan mereka seperti itu.

kontra
sumber
16
Apakah Anda menyadari bahwa Anda tidak perlu menggunakan tanda kutip tunggal untuk alias? Kutipan ganda jauh lebih mudah.
teknopaul
3
Kutipan ganda bersarang lolos,, "\""jadi itu harus digunakan sebagai preferensi terhadap jawaban @ liori bila memungkinkan.
alan
7
Kutipan ganda berperilaku sangat berbeda dari tanda kutip tunggal di * nix (termasuk Bash, dan alat terkait seperti Perl), jadi mengganti kutip ganda setiap kali ada masalah dengan tanda kutip tunggal BUKAN solusi yang baik. Kutipan ganda menentukan variabel $ ... harus diganti sebelum dieksekusi, sedangkan tanda kutip tunggal menentukan $ ... harus diperlakukan secara harfiah.
Chuck Kollars
Jika Anda berpikir, saya menggunakan tanda kutip ganda tetapi masih tidak berfungsi , sumber skrip Anda lagi.
Samy Bencherif

Jawaban:

1456

Jika Anda benar-benar ingin menggunakan tanda kutip tunggal di lapisan terluar, ingatlah bahwa Anda dapat merekatkan kedua jenis kutipan tersebut. Contoh:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Penjelasan tentang bagaimana '"'"'ditafsirkan sebagai adil ':

  1. ' Akhiri kutipan pertama yang menggunakan kutipan tunggal.
  2. " Mulai kutipan kedua, menggunakan tanda kutip ganda.
  3. ' Karakter yang dikutip.
  4. " Akhiri kutipan kedua, menggunakan tanda kutip ganda.
  5. ' Mulai kutipan ketiga, menggunakan kutipan tunggal.

Jika Anda tidak menempatkan spasi putih antara (1) dan (2), atau antara (4) dan (5), shell akan menafsirkan string itu sebagai satu kata yang panjang.

liori
sumber
5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Ini bekerja ketika ada tanda kutip tunggal dan ganda di string alias!
Uphill_ What '1
17
Interpretasi saya: bash secara implisit menggabungkan ekspresi string yang dikutip berbeda.
Benjamin Atkin
2
bekerja untuk saya, contoh tanda kutip tunggal lolos ganda:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco
2
Tentu bukan solusi yang paling mudah dibaca. Itu terlalu menggunakan tanda kutip tunggal di mana mereka tidak benar-benar diperlukan.
Oberlies
26
Saya berpendapat bahwa '\''itu jauh lebih mudah dibaca di sebagian besar konteks daripada '"'"'. Bahkan, yang pertama hampir selalu jelas berbeda dalam string yang dikutip tunggal, dan dengan demikian hanya masalah memetakannya secara semantik dengan makna "itu kutipan yang lolos", seperti halnya dengan \"string yang dikutip ganda. Sedangkan yang terakhir menyatu dalam satu baris kutu kutipan dan perlu pemeriksaan cermat dalam banyak kasus untuk membedakan dengan benar.
mtraceur
263

Saya selalu mengganti setiap kutipan tunggal yang disematkan dengan urutan: '\''(yaitu: kutipan kutipan backslash) yang menutup string, menambahkan kutipan tunggal yang lolos dan membuka kembali string.


Saya sering menyiapkan fungsi "kutip" dalam skrip Perl saya untuk melakukan ini untuk saya. Langkah-langkahnya adalah:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

Ini cukup banyak menangani semua kasus.

Hidup menjadi lebih menyenangkan ketika Anda memperkenalkan evalskrip shell Anda. Anda pada dasarnya harus mengutip kembali semuanya lagi!

Misalnya, buat skrip Perl yang disebut quotify yang berisi pernyataan di atas:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

lalu gunakan untuk menghasilkan string yang dikutip dengan benar:

$ quotify
urxvt -fg '#111111' -bg '#111111'

hasil:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

yang kemudian dapat disalin / ditempelkan ke perintah alias:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(Jika Anda perlu memasukkan perintah ke eval, jalankan kembali kutipannya:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

hasil:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

yang dapat disalin / ditempelkan ke eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
Adrian Pronk
sumber
1
Tapi ini bukan perl. Dan seperti yang ditunjukkan Steve B di atas, dengan rujukannya ke "manual referensi gnu", Anda tidak dapat menghindari tanda kutip di bash dalam jenis kutipan yang sama. Dan pada kenyataannya, tidak perlu melarikan diri dari mereka dalam tanda kutip alternatif, misalnya "'" adalah string kutipan tunggal yang valid dan' "'adalah string kutipan ganda yang valid tanpa memerlukan pelarian.
nicerobot
8
@nicerobot: Saya telah menambahkan contoh yang menunjukkan bahwa: 1) Saya tidak berusaha untuk keluar dari kutipan dengan jenis kutipan yang sama, 2) atau dalam kutipan alternatif, dan 3) Perl digunakan untuk mengotomatiskan proses menghasilkan valid bash string containg tanda kutip tertanam
Adrian Pronk
18
Paragraf pertama dengan sendirinya adalah jawaban yang saya cari.
Dave Causey
9
Inilah yang dilakukan bash juga, ketik set -xdan echo "here's a string"dan Anda akan melihat bahwa bash mengeksekusi echo 'here'\''s a string'. ( set +xuntuk mengembalikan perilaku normal)
arekolek
196

Karena sintaks Bash 2.04$'string' (bukan hanya 'string'; peringatan: jangan bingung dengan $('string')) adalah mekanisme penawaran lain yang memungkinkan urutan pelarian ANSI C-like dan melakukan ekspansi ke versi yang dikutip tunggal.

Contoh sederhana:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

Dalam kasus Anda:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Urutan melarikan diri umum berfungsi seperti yang diharapkan:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Di bawah ini adalah salinan + dokumentasi terkait yang disisipkan dari man bash(versi 4.4):

Kata-kata dalam bentuk $ 'string' diperlakukan secara khusus. Kata diperluas ke string, dengan karakter backslash-escaped diganti sebagaimana ditentukan oleh standar ANSI C. Urutan backslash escape, jika ada, diterjemahkan sebagai berikut:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

Hasil diperluas dikutip tunggal, seolah-olah tanda dolar tidak ada.


Lihat Kutipan dan melarikan diri: ANSI C menyukai string di wiki bash-hackers.org untuk detail lebih lanjut. Perhatikan juga bahwa file "Perubahan Bash" ( tinjauan umum di sini ) menyebutkan banyak perubahan dan perbaikan bug yang terkait dengan $'string'mekanisme penawaran.

Menurut unix.stackexchange.com Bagaimana cara menggunakan karakter khusus seperti yang normal? itu harus bekerja (dengan beberapa variasi) di bash, zsh, mksh, ksh93 dan FreeBSD dan busybox sh.

mj41
sumber
dapat digunakan tetapi string yang dikutip di sini bukanlah yang dikutip secara nyata, konten pada string ini dapat diinterpretasikan oleh shell: echo $'foo\'b!ar'=> !ar': event not found
regilero
2
Di mesin saya > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41
1
Ya, itulah alasan "mungkin", saya memilikinya di Red hat 6.4, tentu saja versi bash yang lebih tua.
regilero
Bash ChangeLog berisi banyak perbaikan bug yang terkait dengan $'jadi mungkin cara termudah adalah dengan mencobanya sendiri di sistem yang lebih lama.
mj41
menyadari: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Diambil dari tiswww.case.edu/php/chet/bash/CHANGES . Masih berfungsi di 4.3.42 tetapi tidak di 4.3.48.
stiller_leser
49

Saya tidak melihat entri di blognya (tautan pls?) Tetapi menurut manual referensi gnu :

Menutup karakter dalam tanda kutip tunggal ('' ') mempertahankan nilai literal dari setiap karakter dalam tanda kutip. Kutipan tunggal mungkin tidak terjadi di antara tanda kutip tunggal, bahkan ketika didahului oleh garis miring terbalik.

jadi bash tidak akan mengerti:

alias x='y \'z '

namun, Anda dapat melakukan ini jika dikelilingi dengan tanda kutip ganda:

alias x="echo \'y "
> x
> 'y
Steve B.
sumber
Isi terlampir dengan tanda kutip ganda sedang dievaluasi sehingga melampirkan hanya tanda kutip tunggal dalam tanda kutip ganda seperti yang disarankan oleh liori tampaknya menjadi solusi yang tepat.
Piotr Dobrogost
3
Ini adalah jawaban aktual untuk pertanyaan itu. Meskipun jawaban yang diterima dapat memberikan solusi, secara teknis menjawab pertanyaan yang tidak ditanyakan.
Matius G
3
Matius, pertanyaannya adalah tentang melepaskan satu tanda kutip di dalam tanda kutip tunggal. Jawaban ini meminta pengguna untuk mengubah perilaku mereka, dan jika Anda memiliki hambatan untuk menggunakan tanda kutip ganda (seperti yang disarankan judul pertanyaan), jawaban ini tidak akan membantu. Ini cukup berguna meskipun (Meskipun jelas), dan dengan demikian layak mendapatkan upvote, tetapi jawaban yang diterima membahas masalah tepat yang ditanyakan Op.
Fernando Cordeiro
Tidak perlu mengutip kutipan tunggal dalam string kutipan ganda.
Matthew D. Scholefield
32

Saya dapat mengonfirmasi bahwa menggunakan '\''untuk kutipan tunggal di dalam string kutipan tunggal tidak bekerja di Bash, dan itu dapat dijelaskan dengan cara yang sama dengan argumen "menempelkan" dari sebelumnya di utas. Misalkan kita memiliki string yang dikutip: 'A '\''B'\'' C'(semua kutipan di sini adalah tanda kutip tunggal). Jika diteruskan ke gema, mencetak berikut: A 'B' C. Di setiap '\''kutipan pertama menutup string yang dikutip tunggal, yang berikut ini \'menempelkan kutipan tunggal ke string sebelumnya ( \'adalah cara untuk menentukan kutipan tunggal tanpa memulai string yang dikutip), dan kutipan terakhir membuka string yang dikutip tunggal.

Mikhail di YugaByte
sumber
2
Ini menyesatkan, sintaks ini '\' 'tidak masuk "ke dalam" string yang dikutip tunggal. Dalam pernyataan ini 'A' \ '' B '\' 'C' Anda menggabungkan 5 \ pelarian dan satu tanda kutip string
teknopaul
1
@teknopaul Penugasan alias something='A '\''B'\'' C'tersebut menghasilkan somethingstring tunggal, jadi meskipun sisi kanan dari penugasan tersebut tidak secara teknis berupa string tunggal, saya tidak menganggapnya penting.
Teemu Leisti
Meskipun ini berfungsi dalam contoh Anda, secara teknis tidak memberikan solusi untuk cara memasukkan kutipan tunggal ke dalam string yang dikutip. Anda sudah menjelaskannya, tapi ya berhasil 'A ' + ' + 'B' + ' + ' C'. Dengan kata lain, solusi untuk memasukkan karakter kutipan tunggal ke dalam string yang dikutip tunggal harus memungkinkan saya untuk membuat string seperti itu dengan sendirinya, dan mencetaknya. Namun solusi ini tidak akan berfungsi dalam kasus ini. STR='\''; echo $STR. Seperti yang dirancang, BASH tidak benar-benar mengizinkan ini.
krb686
@mikhail_b, ya, '\''bekerja untuk bash. Bisakah Anda menunjukkan bagian gnu.org/software/bash/manual/bashref.html yang menentukan perilaku seperti itu?
Jingguo Yao
20

Kedua versi berfungsi, baik dengan penggabungan dengan menggunakan karakter tanda kutip tunggal yang lolos (\ '), atau dengan gabungan dengan melampirkan karakter kutipan tunggal dalam tanda kutip ganda ("'").

Penulis pertanyaan tidak memperhatikan bahwa ada kutipan tunggal tambahan (') pada akhir upaya pelarian terakhirnya:

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

Seperti yang dapat Anda lihat di karya seni ASCII / Unicode yang bagus sebelumnya, kutipan tunggal yang lolos terakhir (\ ') diikuti oleh kutipan tunggal yang tidak perlu ('). Menggunakan sintaks-stabilo seperti yang ada di Notepad ++ dapat terbukti sangat membantu.

Hal yang sama berlaku untuk contoh lain seperti yang berikut:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

Dua contoh alias yang indah ini menunjukkan dengan cara yang sangat rumit dan membingungkan bagaimana sebuah file dapat dijajarkan. Artinya, dari file dengan banyak baris Anda hanya mendapatkan satu baris dengan koma dan spasi di antara isi baris sebelumnya. Untuk memahami komentar sebelumnya, berikut ini adalah contohnya:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214
leftaroundabout
sumber
3
Ditulis untuk ilustrasi Tabel ASCII :)
php-dev
16

Contoh sederhana untuk menghindari kutip di shell:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

Ini dilakukan dengan menyelesaikan yang sudah dibuka satu ( '), menempatkan satu lolos ( \'), lalu membuka satu lagi ( '). Sintaks ini berfungsi untuk semua perintah. Ini pendekatan yang sangat mirip dengan jawaban pertama.

kenorb
sumber
15

Saya tidak secara khusus menangani masalah mengutip karena, yah, kadang-kadang, masuk akal untuk mempertimbangkan pendekatan alternatif.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

yang kemudian dapat Anda panggil sebagai:

rxvt 123456 654321

gagasannya adalah bahwa Anda sekarang dapat alias ini tanpa memperhatikan kutipan:

alias rxvt='rxvt 123456 654321'

atau, jika Anda perlu memasukkan #semua panggilan untuk beberapa alasan:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

yang kemudian dapat Anda panggil sebagai:

rxvt '#123456' '#654321'

maka, tentu saja, alias adalah:

alias rxvt="rxvt '#123456' '#654321'"

(Ups, saya kira saya agak mengatasi kutipannya :)

nicerobot
sumber
1
Saya mencoba untuk memasukkan sesuatu ke dalam tanda kutip tunggal yang dalam tanda kutip ganda yang, pada gilirannya, dalam tanda kutip tunggal. Astaga. Terima kasih atas jawaban Anda "coba pendekatan yang berbeda". Itu membuat perbedaan.
Clinton Blackmore
1
Saya terlambat 5 tahun, tetapi bukankah Anda melewatkan satu kutipan di alias terakhir Anda?
Julien
1
@ Julien Saya tidak melihat masalah ;-)
nicerobot
11

Karena seseorang tidak dapat menempatkan tanda kutip tunggal dalam string yang dikutip tunggal, opsi paling sederhana dan paling mudah dibaca adalah menggunakan string HEREDOC

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

Dalam kode di atas, HEREDOC dikirim ke catperintah dan output yang ditugaskan ke variabel melalui notasi substitusi perintah$(..)

Menempatkan satu kutipan di sekitar HEREDOC diperlukan karena berada dalam a $()

Nerrve
sumber
Saya berharap saya telah menggulir sejauh ini sebelumnya - saya menemukan kembali pendekatan ini dan datang ke sini untuk mempostingnya! Ini jauh lebih bersih dan lebih mudah dibaca daripada semua pendekatan melarikan diri lainnya. Bukan itu tidak akan bekerja pada beberapa shell non-bash, seperti dashyang merupakan shell default di skrip pemula Ubuntu dan di tempat lain.
Korny
Terima kasih! bahwa apa yang saya cari, cara untuk mendefinisikan perintah seperti melalui heredoc dan meneruskan perintah otomatis lolos ke ssh. BTW cat << PERINTAH tanpa tanda kutip memungkinkan untuk menginterpolasi variabel di dalam perintah dan berfungsi juga untuk pendekatan ini.
Igor Tverdovskiy
10

Saya hanya menggunakan kode shell .. misalnya \x27atau yang \\x22berlaku. Tidak ada kerumitan, sungguh.

Rob Jens
sumber
Bisakah Anda menunjukkan contoh ini dalam operasi? Bagi saya itu hanya mencetak literal x27(pada Centos 6.6)
Will Sheppard
6

Sebagian besar jawaban ini mengenai kasus spesifik yang Anda tanyakan. Ada pendekatan umum yang teman dan saya telah dikembangkan yang memungkinkan untuk sewenang-wenang mengutip dalam kasus Anda perlu untuk kutipan pesta perintah melalui beberapa lapisan ekspansi shell, misalnya, melalui ssh, su -c, bash -c, dll Ada satu inti primitif yang Anda butuhkan, di sini dalam bash asli:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

Ini tidak persis seperti yang dikatakannya: ia mengutip setiap argumen secara individual (tentu saja setelah ekspansi bash):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

Ini melakukan hal yang jelas untuk satu lapisan ekspansi:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Perhatikan bahwa tanda kutip ganda $(quote_args ...)diperlukan untuk menjadikan hasilnya menjadi argumen tunggal bash -c.) Dan ini dapat digunakan secara lebih umum untuk mengutip dengan benar melalui beberapa lapis ekspansi:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

Contoh di atas:

  1. shell-quote setiap argumen ke inner secara quote_argsindividual dan kemudian menggabungkan output yang dihasilkan menjadi argumen tunggal dengan inner double quotes.
  2. shell-quotes bash,, -cdan hasil yang sudah pernah dikutip dari langkah 1, dan kemudian menggabungkan hasilnya menjadi argumen tunggal dengan tanda kutip ganda luar.
  3. mengirimkan kekacauan itu sebagai argumen ke luar bash -c.

Singkatnya, gagasan itu. Anda dapat melakukan beberapa hal yang cukup rumit dengan ini, tetapi Anda harus berhati-hati tentang urutan evaluasi dan tentang substring mana yang dikutip. Misalnya, berikut ini melakukan hal-hal yang salah (untuk beberapa definisi "salah"):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

Pada contoh pertama, bash segera mengembang quote_args cd /; pwd 1>&2menjadi dua perintah terpisah, quote_args cd /dan pwd 1>&2, jadi CWD masih /tmpketika pwdperintah dieksekusi. Contoh kedua menggambarkan masalah yang sama untuk globbing. Memang, masalah dasar yang sama terjadi dengan semua ekspansi bash. Masalahnya di sini adalah substitusi perintah bukanlah pemanggilan fungsi: ini secara harfiah mengevaluasi satu skrip bash dan menggunakan outputnya sebagai bagian dari skrip bash lain.

Jika Anda mencoba melarikan diri dari operator shell, Anda akan gagal karena string yang dihasilkan diteruskan bash -c hanya rangkaian string yang dikutip secara individual yang tidak kemudian diartikan sebagai operator, yang mudah dilihat jika Anda menggemakan string yang akan telah diteruskan ke bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

Masalahnya di sini adalah Anda terlalu banyak mengutip. Yang Anda butuhkan adalah agar operator tidak mengutip sebagai input pada lampiranbash -c , yang berarti mereka harus berada di luar $(quote_args ...)substitusi perintah.

Akibatnya, apa yang perlu Anda lakukan dalam pengertian yang paling umum adalah dengan mengutip setiap kata dari perintah yang tidak dimaksudkan untuk diperluas pada saat penggantian perintah secara terpisah, dan tidak menerapkan penawaran tambahan untuk operator shell:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Setelah Anda melakukan ini, seluruh string adalah permainan yang adil untuk mengutip lebih lanjut ke tingkat evaluasi sewenang-wenang:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

dll.

Contoh-contoh ini mungkin kelihatan berlebihan mengingat bahwa kata-kata seperti success,, sbindan pwdtidak perlu dikutip dengan shell, tetapi poin kunci yang perlu diingat ketika menulis sebuah skrip mengambil input sewenang-wenang adalah bahwa Anda ingin mengutip semua yang Anda tidak benar-benar yakin tidak. ' tidak perlu mengutip, karena Anda tidak pernah tahu kapan pengguna akan melempar Robert'; rm -rf /.

Untuk lebih memahami apa yang terjadi di balik selimut, Anda dapat bermain-main dengan dua fungsi pembantu kecil:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

yang akan menyebutkan setiap argumen ke perintah sebelum menjalankannya:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2
Kyle Rose
sumber
Hai Kyle. Solusi Anda bekerja untuk kasus saya, ketika saya membutuhkan untuk lulus sekelompok argumen sebagai argumen tunggal: vagrant ssh -c {single-arg} guest. The {single-arg}kebutuhan untuk diperlakukan sebagai arg tunggal karena gelandangan mengambil arg berikutnya setelah sebagai nama tamu. Pesanan tidak dapat diubah. Tetapi saya perlu menyampaikan perintah dan argumennya di dalam {single-arg}. Jadi saya menggunakan Anda quote_args()untuk mengutip perintah dan args nya, dan meletakkan tanda kutip ganda di sekitar hasilnya, dan bekerja seperti pesona: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Terima kasih!!!
Andreas Maier
6

IMHO jawaban sebenarnya adalah bahwa Anda tidak dapat menghindari tanda kutip tunggal dalam string yang dikutip satu kali.

Tidak mungkin.

Jika kami kira kami menggunakan bash.

Dari manual bash ...

Enclosing characters in single quotes preserves the literal value of each
character within the quotes.  A single quote may not occur
between single quotes, even when preceded by a backslash.

Anda perlu menggunakan salah satu dari mekanisme pelarian string lain "atau \

Tidak ada keajaiban tentang itu alias itu yang menuntutnya menggunakan tanda kutip tunggal.

Keduanya bekerja di bash berikut.

alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'

Yang terakhir menggunakan \ untuk keluar dari karakter spasi.

Tidak ada keajaiban tentang # 111111 yang membutuhkan tanda kutip tunggal.

Opsi-opsi berikut menghasilkan hasil yang sama dengan dua opsi lainnya, di mana rxvt alias berfungsi seperti yang diharapkan.

alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""

Anda juga dapat menghindari # yang merepotkan secara langsung

alias rxvt="urxvt -fg \#111111 -bg \#111111"
teknopaul
sumber
"Jawaban sebenarnya adalah bahwa Anda tidak dapat melarikan diri dari tanda kutip tunggal dalam string yang dikutip sendiri." Secara teknis itu benar. Tetapi Anda dapat memiliki solusi yang dimulai dengan kutipan tunggal, diakhiri dengan kutipan tunggal, dan hanya berisi kutipan tunggal di tengah. stackoverflow.com/a/49063038
wisbucky
Bukan dengan melarikan diri, hanya dengan penggabungan.
teknopaul
4

Dalam contoh yang diberikan, cukup gunakan tanda kutip ganda dan bukan tanda kutip tunggal sebagai mekanisme pelarian luar:

alias rxvt="urxvt -fg '#111111' -bg '#111111'"

Pendekatan ini cocok untuk banyak kasus di mana Anda hanya ingin meneruskan string tetap ke perintah: Cukup periksa bagaimana shell akan menafsirkan string yang dikutip ganda melalui echo , dan melarikan diri karakter dengan backslash jika perlu.

Pada contoh, Anda akan melihat bahwa tanda kutip ganda cukup untuk melindungi string:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'
oberlies
sumber
4

Jelas, akan lebih mudah untuk dikelilingi dengan tanda kutip ganda, tetapi di mana tantangannya? Inilah jawabannya dengan hanya menggunakan satu kutipan. Saya menggunakan variabel alih-alih aliasagar lebih mudah untuk mencetak bukti, tetapi sama dengan menggunakan alias.

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

Penjelasan

Kuncinya adalah Anda dapat menutup kutipan tunggal dan membukanya kembali sebanyak yang Anda inginkan. Misalnya foo='a''b'sama dengan foo='ab'. Jadi Anda dapat menutup kutipan tunggal, melemparkan kutipan tunggal literal \', lalu buka kembali kutipan tunggal berikutnya.

Diagram kerusakan

Diagram ini memperjelas dengan menggunakan tanda kurung untuk menunjukkan di mana tanda kutip tunggal dibuka dan ditutup. Kutipan tidak "bersarang" seperti tanda kurung. Anda juga dapat memperhatikan sorotan warna, yang diterapkan dengan benar. String yang dikutip berwarna merah marun, sedangkan senar \'hitam.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(Pada dasarnya ini adalah jawaban yang sama dengan jawaban Adrian, tapi saya rasa ini menjelaskannya dengan lebih baik. Juga jawabannya memiliki 2 kutipan tunggal yang berlebihan di bagian akhir.)

wisbucky
sumber
+1 untuk menggunakan '\''metode ini saya sarankan untuk '"'"'metode yang sering sulit bagi manusia untuk membaca.
mtraceur
3

Berikut adalah uraian tentang Satu Jawaban Sejati yang dirujuk di atas:

Terkadang saya akan mengunduh menggunakan rsync lewat ssh dan harus melepaskan nama file dengan tanda 'di dalamnya DUA KALI! (OMG!) Sekali untuk bash dan sekali untuk ssh. Prinsip yang sama dari pembatas kutipan bergantian bekerja di sini.

Sebagai contoh, katakanlah kita ingin mendapatkan: Cerita LA Louis Theroux ...

  1. Pertama, Anda menyertakan Louis Theroux dalam tanda kutip tunggal untuk bash dan tanda kutip ganda untuk ssh: '"Louis Theroux"'
  2. Kemudian Anda menggunakan tanda kutip tunggal untuk menghindari kutip ganda '"'
  3. Penggunaan tanda kutip ganda untuk keluar dari tanda kutip "" "
  4. Kemudian ulangi # 2, menggunakan tanda kutip tunggal untuk menghindari kutip ganda '"'
  5. Kemudian sertakan LA Stories dalam tanda kutip tunggal untuk bash dan tanda kutip ganda untuk ssh: '"LA Stories"'

Dan lihatlah! Anda berakhir dengan ini:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

yang merupakan banyak sekali pekerjaan untuk satu kecil '- tetapi di sana Anda pergi

PatchyFog
sumber
3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Penjelasan implementasi:

  • tanda kutip ganda sehingga kita dapat dengan mudah menampilkan pembungkus tanda kutip tunggal dan menggunakan ${...}sintaks

  • pencarian dan ganti bash terlihat seperti: ${varname//search/replacement}

  • kami mengganti 'dengan'\''

  • '\''mengkodekan satu 'seperti:

    1. ' mengakhiri kutipan tunggal

    2. \'mengkodekan a '(garis miring terbalik diperlukan karena kita tidak berada di dalam tanda kutip)

    3. ' memulai mengutip tunggal lagi

    4. bash secara otomatis menggabungkan string tanpa spasi putih di antaranya

  • ada \sebelum \dan 'karena itulah aturan untuk melarikan diri ${...//.../...}.

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

PS Selalu mengkodekan ke string yang dikutip tunggal karena mereka jauh lebih sederhana daripada string yang dikutip ganda.

JasonWoof
sumber
2

Cara lain untuk memperbaiki masalah terlalu banyak lapisan kutipan bersarang:

Anda mencoba menjejalkan terlalu banyak ke ruang yang terlalu kecil, jadi gunakan fungsi bash.

Masalahnya adalah Anda mencoba memiliki terlalu banyak level sarang, dan teknologi dasar alias tidak cukup kuat untuk mengakomodasi. Gunakan fungsi bash seperti ini untuk membuatnya jadi kutu tunggal, tanda kutip ganda kembali dan melewati parameter semua ditangani secara normal seperti yang kita harapkan:

lets_do_some_stuff() {
    tmp=$1                       #keep a passed in parameter.
    run_your_program $@          #use all your passed parameters.
    echo -e '\n-------------'    #use your single quotes.
    echo `date`                  #use your back ticks.
    echo -e "\n-------------"    #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Kemudian Anda dapat menggunakan variabel $ 1 dan $ 2 Anda dan tanda kutip tunggal, ganda dan kutu belakang tanpa khawatir tentang fungsi alias merusak integritas mereka.

Program ini mencetak:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------
Eric Leschinski
sumber
2

Jika Anda telah menginstal GNU Parallel Anda dapat menggunakan kutipan internal:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

Dari versi 20190222 Anda bahkan dapat --shellquotebeberapa kali:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

Ini akan mengutip string dalam semua shell yang didukung (tidak hanya bash).

Ole Tange
sumber
1

Fungsi ini:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

memungkinkan mengutip dari 'dalam '. Gunakan seperti ini:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Jika baris untuk mengutip menjadi lebih kompleks, seperti tanda kutip ganda dicampur dengan tanda kutip tunggal, mungkin akan sangat sulit untuk mendapatkan string untuk mengutip dalam suatu variabel. Ketika kasus seperti itu muncul, tulis baris persis yang harus Anda kutip di dalam skrip (mirip dengan ini)

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Akan menghasilkan:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

Semua string yang dikutip dengan benar di dalam kutipan tunggal.


sumber
1

Jika Anda membuat string shell dalam Python 2 atau Python 3, berikut ini dapat membantu untuk mengutip argumen:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

Ini akan menampilkan:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'
George V. Reilly
sumber
1

Berikut adalah dua sen saya - dalam kasus ini jika seseorang ingin menjadi sh-portable, bukan hanya bash-specific (solusinya tidak terlalu efisien, ketika memulai program eksternal - sed):

  • letakkan ini di quote.sh(atau hanya quote) di suatu tempat di PATH:
# ini berfungsi dengan input standar (stdin)
kutipan () {
  echo -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"' '' '/ g';
  echo -n "'"
}

huruf "$ 1" di
 -) kutipan ;;
 *) echo "penggunaan: cat ... | quote - # input tanda kutip tunggal untuk Bourne shell" 2> & 1 ;;
esac

Sebuah contoh:

$ echo -n "G'day, sobat!" | ./quote.sh -
'G' "'"' hari, sobat! '

Dan, tentu saja, itu mengubah kembali:

$ echo 'G' "'"' day, mate! '
G'day, sobat!

Penjelasan: pada dasarnya kita harus melampirkan input dengan tanda kutip ', dan kemudian juga mengganti setiap kutipan tunggal dengan mikro-monster ini: '"'"'(akhiri kutipan pembuka dengan pasangan ', lepaskan kutipan tunggal yang ditemukan dengan membungkusnya dengan tanda kutip ganda - "'", dan kemudian akhirnya mengeluarkan pembukaan tunggal kutipan baru ', atau pseudo-notasi: ' + "'" + ' == '"'"')

Salah satu cara standar untuk melakukan itu adalah dengan menggunakan sedperintah substitusi berikut:

s/\(['][']*\)/'"\1"'/g 

Namun, satu masalah kecil adalah agar dapat menggunakannya dalam shell kita harus keluar dari semua karakter kutipan tunggal ini dalam ekspresi sed itu sendiri - yang mengarah ke sesuatu seperti

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' 

(dan satu cara yang baik untuk membangun hasil ini adalah dengan memberi makan ekspresi asli s/\(['][']*\)/'"\1"'/gke skrip Kyle Rose 'atau George V. Reilly).

Akhirnya, masuk akal untuk mengharapkan input berasal stdin- karena melewati argumen command-line bisa jadi sudah terlalu banyak masalah.

(Oh, dan mungkin kita ingin menambahkan pesan bantuan kecil sehingga skrip tidak hang ketika seseorang hanya menjalankannya ./quote.sh --helpbertanya - tanya apa fungsinya.)

ジ ョ ー ジ
sumber
0

Ini solusi lain. Fungsi ini akan mengambil argumen tunggal dan mengutipnya dengan tepat menggunakan karakter kutipan tunggal, sama seperti jawaban yang dipilih di atas menjelaskan:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

Jadi, Anda bisa menggunakannya dengan cara ini:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
exbuddha
sumber