Come sfuggire alle virgolette singole all'interno di stringhe tra virgolette singole


1018

Diciamo che hai un Bash aliascome:

alias rxvt='urxvt'

che funziona benissimo.

Però:

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

non funzionerà e nemmeno:

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

Quindi, come si fa ad abbinare l'apertura e la chiusura delle virgolette all'interno di una stringa una volta che si è sfuggiti alle virgolette?

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

sembra sgraziato anche se rappresenterebbe la stessa stringa se ti fosse permesso concatenarli in quel modo.


16
Ti rendi conto che non è necessario utilizzare virgolette singole per alias? Le doppie virgolette sono molto più facili.
Teknopaul,


3
Le doppie virgolette nidificate sono evitabili, "\""quindi dovrebbero essere usate in preferenza alla risposta di @ liori ogni volta che è possibile.
alan

7
Le virgolette doppie si comportano in modo abbastanza diverso dalle virgolette singole in * nix (incluso Bash e strumenti correlati come Perl), quindi sostituire le virgolette doppie ogni volta che c'è un problema con le virgolette singole NON è una buona soluzione. Le virgolette doppie specificano $ ... le variabili devono essere sostituite prima dell'esecuzione, mentre le virgolette singole specificano $ ... devono essere trattate alla lettera.
Chuck Kollars,

Se stai pensando, ho usato le virgolette doppie ma non funziona ancora , procedi di nuovo con il tuo script.
Samy Bencherif,

Risposte:


1456

Se vuoi davvero usare le virgolette singole nel livello più esterno, ricorda che puoi incollare entrambi i tipi di citazione. Esempio:

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

Spiegazione di come '"'"'viene interpretato come solo ':

  1. ' Termina la prima citazione che utilizza virgolette singole.
  2. " Inizia la seconda citazione, usando le virgolette doppie.
  3. ' Carattere citato.
  4. " Termina la seconda citazione, usando le virgolette doppie.
  5. ' Inizia la terza citazione, usando le virgolette singole.

Se non si posizionano spazi bianchi tra (1) e (2) o tra (4) e (5), la shell interpreterà quella stringa come un'unica parola lunga.


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Funziona quando ci sono sia virgolette singole che doppie nella stringa alias!
Uphill_ What '1

17
La mia interpretazione: bash concatena implicitamente espressioni di stringa tra virgolette diverse.
Benjamin Atkin,

2
ha funzionato per me, esempio di virgolette singole con doppio escape:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco,

2
Certamente non è la soluzione più leggibile. Sovrascrive le virgolette singole dove non sono realmente necessarie.
oberlies,

26
Io sostengo che '\''è molto più leggibile nella maggior parte dei contesti di '"'"'. In effetti, il primo è quasi sempre chiaramente distinto all'interno di una stringa a virgoletta singola, e quindi si tratta solo di mapparlo semanticamente al significato di "è una citazione sfuggita", come si fa con \"stringhe tra virgolette doppie. Considerando che quest'ultimo si fonde in una riga di virgolette e ha bisogno di un'attenta ispezione in molti casi per distinguere correttamente.
mtraceur,

263

Sostituisco sempre ogni singola virgoletta incorporata con la sequenza: '\''(ovvero: virgolette virgolette virgolette virgolette) che chiude la stringa, accoda una virgoletta singola con escape e riapre la stringa.


Spesso creo una funzione "quotify" nei miei script Perl per fare questo per me. I passaggi sarebbero:

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

Questo praticamente si occupa di tutti i casi.

La vita diventa più divertente quando introduci i evaltuoi script di shell. Devi essenzialmente ri-quotare di nuovo tutto!

Ad esempio, crea uno script Perl chiamato quotify contenente le istruzioni precedenti:

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

quindi usalo per generare una stringa quotata correttamente:

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

risultato:

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

che può quindi essere copiato / incollato nel comando alias:

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

(Se è necessario inserire il comando in una valutazione, eseguire nuovamente il comando:

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

risultato:

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

che può essere copiato / incollato in una valutazione:

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

1
Ma questo non è perl. E come Steve B ha sottolineato sopra, con il suo riferimento al "manuale di riferimento gnu", non puoi sfuggire alle citazioni in bash all'interno dello stesso tipo di citazione. E in effetti, non è necessario sfuggirli tra virgolette alternative, ad esempio "" "è una stringa a virgoletta singola valida e" "" è una stringa a virgoletta doppia valida senza richiedere alcuna escape.
nicerobot

8
@nicerobot: ho aggiunto un esempio che mostra che: 1) Non tento di sfuggire alle virgolette all'interno dello stesso tipo di citazione, 2) né tra virgolette alternative e 3) Perl viene utilizzato per automatizzare il processo di generazione di un valido bash string contenente citazioni incorporate
Adrian Pronk

18
Il primo paragrafo da solo è la risposta che stavo cercando.
Dave Causey,

9
Questo è anche ciò che fa bash, digita set -xe echo "here's a string"vedrai che bash viene eseguito echo 'here'\''s a string'. ( set +xper ritornare al comportamento normale)
arekolek,

196

Poiché la sintassi di Bash 2.04$'string' (invece di solo 'string'; attenzione: non confondere $('string')) è un altro meccanismo di quotazione che consente sequenze di escape simili a C ANSI ed effettua l'espansione alla versione a virgoletta singola.

Esempio semplice:

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

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

Nel tuo caso:

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

Le sequenze di escape comuni funzionano come previsto:

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

Di seguito è disponibile la copia + la documentazione relativa incollata da man bash(versione 4.4):

Le parole del modulo $ 'stringa' sono trattate in modo speciale. La parola si espande in stringa, con i caratteri con escape backslash sostituiti come specificato dallo standard ANSI C. Le sequenze di escape barra rovesciata, se presenti, sono decodificate come segue:

    \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

Il risultato espanso è tra virgolette singole, come se il segno del dollaro non fosse stato presente.


Vedi Citazioni e scappare: ANSI C come stringhe sul wiki di bash-hackers.org per maggiori dettagli. Si noti inoltre che il file "Cambia Bash" ( panoramica qui ) menziona molto le modifiche e le correzioni di bug relative al $'string'meccanismo di quotazione.

Secondo unix.stackexchange.com Come usare un carattere speciale come normale? dovrebbe funzionare (con alcune variazioni) in bash, zsh, mksh, ksh93 e FreeBSD e busybox sh.


potrebbe essere usato ma la singola stringa tra virgolette qui non è una vera virgoletta singola, il contenuto di questa stringa può essere interpretato dalla shell: echo $'foo\'b!ar'=> !ar': event not found
regilero,

2
Sulla mia macchina > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Sì, questo è il motivo di "maggio", l'ho avuto su un cappello rosso 6.4, sicuramente una versione bash precedente.
regilero,

Bash ChangeLog contiene molte correzioni di bug relative al $'modo probabilmente più semplice per provarlo da soli sui sistemi più vecchi.
mj41,

attenzione: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.tratto da tiswww.case.edu/php/chet/bash/CHANGES . Funziona ancora in 4.3.42 ma non in 4.3.48.
stiller_leser,

49

Non vedo la voce sul suo blog (link pls?) Ma secondo il manuale di riferimento di gnu :

Racchiudere i caratteri tra virgolette singole ('' ') conserva il valore letterale di ciascun carattere tra virgolette. Tra virgolette singole non può verificarsi una virgoletta, anche se preceduta da una barra rovesciata.

quindi bash non capirà:

alias x='y \'z '

tuttavia, puoi farlo se ti circondi tra virgolette doppie:

alias x="echo \'y "
> x
> 'y


I contenuti racchiusi tra virgolette doppie vengono valutati, pertanto racchiudere solo virgolette singole tra virgolette doppie come suggerito da liori sembra essere la soluzione corretta.
Piotr Dobrogost,

3
Questa è la risposta effettiva alla domanda. Mentre la risposta accettata può fornire una soluzione, sta tecnicamente rispondendo a una domanda che non è stata posta.
Matthew G,

3
Matthew, la domanda riguardava la fuga da virgolette singole all'interno di virgolette singole. Questa risposta chiede all'utente di modificare il proprio comportamento e se si ha un impedimento all'utilizzo delle virgolette doppie (come suggerisce il titolo della domanda), questa risposta non sarebbe di aiuto. È piuttosto utile (anche se ovvio), e come tale merita un voto, ma la risposta accettata affronta il preciso problema di cui ha chiesto Op.
Fernando Cordeiro,

Non è necessario citare una virgoletta singola in una stringa di virgolette doppie.
Matthew D. Scholefield,

32

Posso confermare che l'utilizzo '\''di una singola virgoletta all'interno di una stringa a virgoletta singola funziona in Bash e può essere spiegato allo stesso modo dell'argomento "incollaggio" di prima nel thread. Supponiamo di avere una stringa tra virgolette: 'A '\''B'\'' C'(tutte le virgolette qui sono virgolette singole). Se si passa a eco, stampa il seguente: A 'B' C. In ciascuna '\''la prima virgoletta chiude la stringa corrente tra virgolette singole, la seguente \'incolla una virgoletta singola nella stringa precedente ( \'è un modo per specificare una virgoletta singola senza iniziare una stringa tra virgolette) e l'ultima citazione apre un'altra stringa tra virgolette singole.


2
Questo è fuorviante, questa sintassi '\' 'non va "dentro" una singola stringa tra virgolette. In questa affermazione 'A' \ '' B '\' 'C' stai concatenando 5 \ escape e stringhe di virgolette singole
teknopaul

1
@teknopaul L'assegnazione alias something='A '\''B'\'' C'si traduce in somethinguna singola stringa, quindi anche se il lato destro dell'assegnazione non è tecnicamente una singola stringa, non penso che importi molto.
Teemu Leisti,

Mentre questo funziona nel tuo esempio, non fornisce tecnicamente una soluzione su come inserire una singola virgoletta all'interno di una singola stringa tra virgolette. L'hai già spiegato, ma sì, lo sta facendo 'A ' + ' + 'B' + ' + ' C'. In altre parole, una soluzione per l'inserimento di caratteri di virgoletta singola all'interno di una stringa a virgoletta singola dovrebbe consentirmi di creare tale stringa da sola e stamparla. Tuttavia, questa soluzione non funzionerà in questo caso. STR='\''; echo $STR. Come progettato, BASH non lo consente davvero.
krb686,

@mikhail_b, sì, '\''funziona per bash. Potresti indicare quali sezioni di gnu.org/software/bash/manual/bashref.html specificano un simile comportamento?
Jingguo Yao,

20

Entrambe le versioni funzionano, sia con concatenazione utilizzando il carattere di virgoletta singola con escape (\ '), sia con concatenazione racchiudendo il carattere di virgoletta singola tra virgolette doppie ("'").

L'autore della domanda non ha notato che c'era una virgoletta singola extra (') alla fine del suo ultimo tentativo di fuga:

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

Come puoi vedere nel precedente bel pezzo di arte ASCII / Unicode, l'ultima virgoletta singola con escape (\ ') è seguita da una virgoletta singola non necessaria ('). L'uso di un evidenziatore di sintassi come quello presente in Notepad ++ può rivelarsi molto utile.

Lo stesso vale per un altro esempio come il seguente:

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

Questi due splendidi esempi di alias mostrano in modo molto intricato e offuscato come si può allineare un file. Cioè, da un file con molte righe si ottiene solo una riga con virgole e spazi tra i contenuti delle righe precedenti. Per dare un senso al commento precedente, il seguente è un esempio:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Upgrade per l'illustrazione della tabella ASCII :)
php-dev

16

Semplice esempio di virgolette di escape nella shell:

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

Viene fatto finendo uno già aperto ( '), posizionando uno sfuggito ( \'), quindi aprendo un altro ( '). Questa sintassi funziona per tutti i comandi. È un approccio molto simile alla prima risposta.


15

Non sto affrontando in modo specifico il problema del preventivo perché, beh, a volte è ragionevole considerare un approccio alternativo.

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

che puoi quindi chiamare come:

rxvt 123456 654321

l'idea è che ora puoi alias questo senza preoccuparti delle citazioni:

alias rxvt='rxvt 123456 654321'

oppure, se è necessario includere #tutte le chiamate per qualche motivo:

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

che puoi quindi chiamare come:

rxvt '#123456' '#654321'

quindi, ovviamente, un alias è:

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

(oops, immagino di aver affrontato la citazione :)


1
Stavo cercando di inserire qualcosa tra virgolette singole tra virgolette doppie, che erano, a loro volta, tra virgolette singole. Yikes. Grazie per la risposta di "provare un approccio diverso". Questo ha fatto la differenza.
Clinton Blackmore,

1
Sono in ritardo di 5 anni, ma non ti manca una sola citazione nel tuo ultimo alias?
Julien,

1
@Julien Non vedo un problema ;-)
nicerobot

11

Poiché non è possibile inserire virgolette singole all'interno di stringhe tra virgolette singole, l'opzione più semplice e più leggibile è utilizzare una stringa HEREDOC

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

alias rxvt=$command

Nel codice sopra, HEREDOC viene inviato al catcomando e l'output di quello viene assegnato a una variabile tramite la notazione di sostituzione del comando$(..)

È necessario inserire una singola citazione intorno a HEREDOC poiché è all'interno di a $()


Vorrei averlo fatto scorrere molto prima: ho reinventato questo approccio e sono venuto qui per pubblicarlo! Questo è molto più pulito e più leggibile di tutti gli altri approcci in fuga. Non funzionerà su alcune shell non bash, come quella dashche è la shell predefinita negli script upstart di Ubuntu e altrove.
Korny,

Grazie! quello che ho cercato, il modo di definire un comando come è tramite heredoc e passare il comando di escape automatico a ssh. BTW cat << COMANDO senza virgolette consente di interpolare vatiables all'interno del comando e funziona anche per questo approccio.
Igor Tverdovskiy

10

Uso solo i codici shell ... ad es. \x27O \\x22come applicabile. Nessun problema, mai davvero.


Potresti mostrare un esempio di questo in funzione? Per me stampa solo un letterale x27(su Centos 6.6)
Will Sheppard,

6

La maggior parte di queste risposte colpisce il caso specifico di cui stai chiedendo. V'è un approccio generale che un amico e ho sviluppato che consente arbitrario citare in caso di necessità di preventivo bash comandi attraverso strati multipli di espansione shell, ad esempio, tramite SSH, su -c, bash -c, ecc C'è un nucleo primitivo è necessario, qui in bash nativo:

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
}

Questo fa esattamente quello che dice: shell cita ogni argomento singolarmente (dopo l'espansione bash, ovviamente):

$ 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'

Fa la cosa ovvia per uno strato di espansione:

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

(Nota che le virgolette doppie $(quote_args ...)sono necessarie per trasformare il risultato in un singolo argomento in bash -c.) E può essere usato più in generale per citare correttamente attraverso più livelli di espansione:

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

L'esempio sopra:

  1. shell cita ogni argomento all'interno quote_argssingolarmente e quindi combina l'output risultante in un singolo argomento con le doppie virgolette interne.
  2. shell-quotes bash, -ce il risultato già citato una volta dal passaggio 1, quindi combina il risultato in un singolo argomento con le doppie virgolette esterne.
  3. invia quel casino come argomento verso l'esterno bash -c.

Questa è l'idea in breve. Puoi fare alcune cose piuttosto complicate con questo, ma devi stare attento all'ordine di valutazione e a quali sottostringhe vengono citate. Ad esempio, quanto segue fa le cose sbagliate (per alcune definizioni di "sbagliato"):

$ (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

Nel primo esempio, bash si espande immediatamente quote_args cd /; pwd 1>&2in due comandi separati quote_args cd /e pwd 1>&2, quindi, il CWD rimane fermo /tmpquando il pwdcomando viene eseguito. Il secondo esempio illustra un problema simile per il globbing. In effetti, lo stesso problema di base si verifica con tutte le espansioni di bash. Il problema qui è che una sostituzione di comando non è una chiamata di funzione: sta letteralmente valutando uno script bash e usando il suo output come parte di un altro script bash.

Se provi semplicemente a sfuggire agli operatori di shell, fallirai perché la stringa risultante passata bash -cè solo una sequenza di stringhe quotate singolarmente che non vengono quindi interpretate come operatori, che è facile vedere se fai eco alla stringa che sono stati passati a 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'

Il problema qui è che stai sopravvalutando. Ciò di cui hai bisogno è che gli operatori non vengano citati come input per l'allegato bash -c, il che significa che devono essere al di fuori della $(quote_args ...)sostituzione del comando.

Di conseguenza, ciò che è necessario fare nel senso più generale è citare una shell per citare ogni parola del comando che non si intende espandere al momento della sostituzione del comando separatamente e non applicare alcuna citazione aggiuntiva agli operatori della 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

Una volta fatto questo, l'intera stringa è un gioco equo per ulteriori citazioni a livelli arbitrari di valutazione:

$ 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

eccetera.

Questi esempi possono sembrare esagerata dato che parole come success, sbine pwdnon hanno bisogno di essere shell citato, ma il punto chiave da ricordare quando si scrive una sceneggiatura prendendo l'input arbitraria è che si vuole citare tutto quello che non si è assolutamente sicuri doesn' Ho bisogno di un preventivo, perché non sai mai quando un utente inserirà un Robert'; rm -rf /.

Per capire meglio cosa sta succedendo sotto le coperte, puoi giocare con due piccole funzioni di supporto:

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

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

che enumererà ogni argomento in un comando prima di eseguirlo:

$ 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

Ciao Kyle. La soluzione ha lavorato molto per un caso che ho avuto, quando avevo bisogno di passare un gruppo di argomenti come un unico argomento: vagrant ssh -c {single-arg} guest. I {single-arg}deve essere trattata come una singola arg perché vagabondo prende la prossima arg dopo che è come il nome dell'ospite. L'ordine non può essere modificato. Ma avevo bisogno di passare un comando e i suoi argomenti all'interno {single-arg}. Così ho usato il tuo quote_args()citare il comando e le sue args, e mettere le virgolette intorno al risultato, e ha funzionato come un fascino: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Grazie!!!
Andreas Maier,

6

La vera risposta è che non puoi sfuggire alle virgolette singole all'interno di stringhe tra virgolette singole.

È impossibile.

Se presumiamo che stiamo usando bash.

Dal manuale di 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.

È necessario utilizzare uno degli altri meccanismi di escape delle stringhe "oppure \

Non c'è nulla di magico in aliasciò che richiede l'uso di virgolette singole.

Entrambi i seguenti lavori in bash.

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

Quest'ultimo sta usando \ per sfuggire al personaggio spaziale.

Non c'è nulla di magico in # 111111 che richiede virgolette singole.

Le seguenti opzioni ottengono lo stesso risultato delle altre due, in quanto l'alias rxvt funziona come previsto.

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

Puoi anche sfuggire direttamente al fastidioso #

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

"la vera risposta è che non puoi sfuggire alle virgolette singole all'interno di stringhe tra virgolette singole." Questo è tecnicamente vero. Ma puoi avere una soluzione che inizia con una virgoletta singola, termina con una virgoletta singola e contiene solo virgolette singole nel mezzo. stackoverflow.com/a/49063038
wisbucky

Non fuggendo, solo concatenando.
Teknopaul,

4

Nell'esempio fornito, sono state semplicemente utilizzate virgolette doppie anziché virgolette singole come meccanismo di escape esterno:

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

Questo approccio è adatto per molti casi in cui si desidera solo passare una stringa fissa a un comando: basta controllare come la shell interpreterà la stringa tra virgolette attraverso un echoe, se necessario, sfuggire ai caratteri con barra rovesciata.

Nell'esempio, vedresti che le doppie virgolette sono sufficienti per proteggere la stringa:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

4

Ovviamente, sarebbe più semplice circondarsi di doppie virgolette, ma dov'è la sfida in questo? Ecco la risposta usando solo virgolette singole. Sto usando una variabile invece aliasche è più facile stampare per prova, ma è lo stesso che usare alias.

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

Spiegazione

La chiave è che puoi chiudere la singola citazione e riaprirla tutte le volte che vuoi. Ad esempio foo='a''b'è lo stesso di foo='ab'. Quindi puoi chiudere la singola citazione, inserire una singola citazione letterale \', quindi riaprire la singola citazione successiva.

Diagramma di ripartizione

Questo diagramma chiarisce usando le parentesi per mostrare dove vengono aperte e chiuse le singole virgolette. Le virgolette non sono "nidificate" come possono essere le parentesi. Puoi anche prestare attenzione all'evidenziazione dei colori, che è applicata correttamente. Le stringhe tra virgolette sono marrone, mentre \'è nera.

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

(Questa è essenzialmente la stessa risposta di Adrian, ma ritengo che questo lo spieghi meglio. Anche la sua risposta ha 2 virgolette superflue alla fine.)


+1 per l'utilizzo del '\''metodo che raccomando rispetto al '"'"'metodo che è spesso più difficile da leggere per gli umani.
mtraceur,

3

Ecco un'elaborazione su L'unica vera risposta di cui sopra:

A volte scaricherò usando rsync su ssh e dovrò sfuggire a un nome di file con un 'in esso DUE VOLTE! (OMG!) Una volta per bash e una volta per ssh. Lo stesso principio di alternatori di delimitatori di quotazioni è al lavoro qui.

Ad esempio, supponiamo di voler ottenere: Le storie di Louis Theroux a Los Angeles ...

  1. Per prima cosa racchiudi Louis Theroux tra virgolette singole per bash e doppie virgolette per ssh: '"Louis Theroux"'
  2. Quindi usi virgolette singole per sfuggire a una doppia virgoletta '"'
  3. L'uso di virgolette doppie per sfuggire all'apostrofo "'"
  4. Quindi ripeti # 2, usando le virgolette singole per evitare una doppia virgoletta '"'
  5. Quindi racchiudere le storie di LA tra virgolette singole per bash e doppie virgolette per ssh: '"LA Stories"'

Ed ecco! Concludete con questo:

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

che è un lavoro terribile per un po '- ma il gioco è fatto


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

Spiegazione di attuazione:

  • virgolette doppie in modo da poter generare facilmente il wrapping di virgolette singole e utilizzare la ${...}sintassi

  • La ricerca e la sostituzione di bash è simile a: ${varname//search/replacement}

  • stiamo sostituendo 'con'\''

  • '\''codifica un singolo in questo 'modo:

    1. ' termina la citazione singola

    2. \'codifica a '(la barra rovesciata è necessaria perché non siamo tra virgolette)

    3. ' avvia nuovamente la virgoletta singola

    4. bash concatena automaticamente le stringhe senza spazi bianchi tra

  • c'è un \prima di ogni \e 'perché sono le regole di fuga per ${...//.../...}.

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

PS Codifica sempre in stringhe tra virgolette singole perché sono molto più semplici delle stringhe tra virgolette doppie.


2

Un altro modo per risolvere il problema di troppi livelli di offerta nidificata:

Stai cercando di stipare troppo in uno spazio troppo piccolo, quindi usa una funzione bash.

Il problema è che stai cercando di avere troppi livelli di annidamento e la tecnologia di alias di base non è abbastanza potente da adattarsi. Usa una funzione bash come questa per renderla così le singole, doppie virgolette indietro tick e parametri passati sono tutte gestite normalmente come ci aspetteremmo:

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

Quindi è possibile utilizzare le variabili $ 1 e $ 2 e le virgolette singole, doppie e le tick posteriori senza preoccuparsi della funzione alias che ne rovina l'integrità.

Questo programma stampa:

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

2

Se hai GNU Parallel installato puoi usare il suo preventivo interno:

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

Dalla versione 20190222 puoi anche --shellquotepiù volte:

$ 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

Citerà la stringa in tutte le shell supportate (non solo bash).


1

Questa funzione:

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

permette la citazione di 'dentro '. Utilizzare come questo:

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

Se la linea tra virgolette diventa più complessa, come le virgolette doppie mescolate con virgolette singole, può diventare abbastanza complicato far sì che la stringa venga citata all'interno di una variabile. Quando si presentano tali casi, scrivi la riga esatta che devi citare all'interno di uno script (simile a questo).

#!/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_

Uscita:

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

Tutte le stringhe correttamente quotate all'interno di virgolette singole.


1

Se stai generando la stringa di shell in Python 2 o Python 3, quanto segue può aiutare a citare gli argomenti:

#!/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()]))

Questo produrrà:

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

1

Ecco i miei due centesimi - nel caso in cui si desideri essere sh-portabili, non solo bashspecifici (la soluzione non è troppo efficiente, dato che avvia un programma esterno - sed):

  • inseriscilo quote.sh(o semplicemente quote) da qualche parte sul tuo PATH:
# funziona con input standard (stdin)
citazione() {
  echo -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"'" '/ g';
  echo -n "'"
}

caso "$ 1" in
 -) citazione ;;
 *) echo "use: cat ... | quote - # input per virgolette singole per la shell Bourne" 2> & 1 ;;
esac

Un esempio:

$ echo -n "Buongiorno, amico!" | ./quote.sh -
'G' "'"' giorno, amico! '

E, naturalmente, questo si converte indietro:

$ echo 'G' "'"' giorno, amico! '
Buongiorno amico!

Spiegazione: fondamentalmente dobbiamo racchiudere l'input tra virgolette ', quindi sostituire anche ogni singola virgoletta all'interno con questo micro-mostro: '"'"'(termina la virgoletta iniziale con un abbinamento ', sfugge alla virgoletta singola trovata avvolgendola con virgolette doppie "'", e poi finalmente rilasciare un nuovo singolo citazione di apertura ', o pseudo-notazione: ' + "'" + ' == '"'"')

Un modo standard per farlo sarebbe usare sedcon il seguente comando di sostituzione:

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

Un piccolo problema, tuttavia, è che per usarlo nella shell è necessario sfuggire a tutti questi caratteri a virgoletta singola nell'espressione sed stessa: cosa porta a qualcosa di simile

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

(e un buon modo per costruire questo risultato è quello di alimentare l'espressione originale s/\(['][']*\)/'"\1"'/galle sceneggiature di Kyle Rose o George V. Reilly).

Infine, ha senso aspettarsi che arrivi l'input, dal stdinmomento che passarlo attraverso gli argomenti della riga di comando potrebbe essere già troppo disturbo.

(Oh, e forse vogliamo aggiungere un piccolo messaggio di aiuto in modo che lo script non si blocchi quando qualcuno lo esegue semplicemente ./quote.sh --helpchiedendosi cosa fa.)


0

Ecco un'altra soluzione. Questa funzione prenderà un singolo argomento e lo citerà appropriatamente usando il carattere a virgoletta singola, proprio come spiega la risposta votata sopra:

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'"
}

Quindi, puoi usarlo in questo modo:

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

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.