Espansione di variabili tra virgolette singole in un comando in Bash


393

Voglio eseguire un comando da a script bash che ha virgolette singole e alcuni altri comandi all'interno delle virgolette singole e una variabile.

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

In questo formato, $ viene eseguito l'escaping e la variabile non viene espansa.

Ho provato le seguenti varianti ma sono state respinte:

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

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

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

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

Se sostituisco il valore al posto della variabile il comando viene eseguito bene.

Per favore, dimmi dove sto sbagliando.


33
repo forall -c ' ...before... '"$variable"' ...after...'
n. 'pronomi' m.

2
@nm le virgolette singole fanno parte del comando repo. non credo che funzionerà
Rachit,

1
bashmangia virgolette singole. O non ci sei bash, oppure le virgolette singole non fanno parte del repocomando.
n. 'pronomi' m.

Non sono sicuro di averti, ma nel caso in cui siano necessarie virgolette singole, non repository forall -c "'... before ..." $ variabile "... after ...'" funziona?
ecv

È uno script bash in esecuzione nella shell bash e il comando repo forall accetta i parametri tra virgolette singole. Se sostituisco il valore effettivo, il comando funziona correttamente.
Rachit,

Risposte:


621

All'interno delle virgolette singole tutto viene conservato letteralmente, senza eccezioni.

Ciò significa che devi chiudere le virgolette, inserire qualcosa e quindi reinserire di nuovo.

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

La concatenazione delle parole è fatta semplicemente dalla giustapposizione. Come puoi verificare, ciascuna delle righe sopra è una singola parola per la shell. Le virgolette (virgolette singole o doppie, a seconda della situazione) non isolano le parole. Sono usati solo per disabilitare l'interpretazione di vari caratteri speciali, come gli spazi bianchi $, ;... Per un buon tutorial sulla citazione vedi la risposta di Mark Reed. Anche rilevante: quali personaggi devono essere evasi in bash?

Non concatenare le stringhe interpretate da una shell

Dovresti assolutamente evitare di creare comandi shell concatenando le variabili. Questa è una cattiva idea simile alla concatenazione di frammenti SQL (SQL injection!).

Di solito è possibile avere segnaposto nel comando e fornire il comando insieme alle variabili in modo che il chiamante possa riceverle dall'elenco degli argomenti di invocazione.

Ad esempio, quanto segue non è molto sicuro. NON FARE QUESTO

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

Se il contenuto di $myvarnon è attendibile, ecco un exploit:

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

Invece della precedente chiamata, utilizzare gli argomenti posizionali. La seguente chiamata è migliore, non sfruttabile:

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

Nota l'uso di singole tick nell'assegnazione a script, il che significa che è preso alla lettera, senza espansione variabile o qualsiasi altra forma di interpretazione.


@ Jo.SO non funzionerà. perché il comando repo prenderà solo i parametri fino alla chiusura delle prime virgolette. Qualunque cosa successiva verrà ignorata per il repository e genererà un altro errore.
Rachit,

8
@Rachit - la shell non funziona così. "some"thing' like'thisè tutta una parola per la shell. Le virgolette non terminano nulla per quanto riguarda l'analisi, e non c'è modo per un comando chiamato dalla shell di dire cosa è stato citato.
Mark Reed,

3
Quella seconda frase ("non puoi nemmeno sfuggire alle virgolette singole") rende le virgolette singole i buchi neri della Shell.

@Evert: non so come dirlo meglio. Ho rimosso la frase.
Jo So

@JoSo È un peccato: no lo scherzo cade a terra.

96

Al repocomando non può importare il tipo di virgolette che riceve. Se hai bisogno di espansione dei parametri, usa virgolette doppie. Se ciò significa che devi finire con il backslash di molte cose, utilizzare virgolette singole per la maggior parte di esse, quindi uscire da loro e andare in doppio per la parte in cui è necessaria l'espansione.

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

La spiegazione segue, se sei interessato.

Quando si esegue un comando dalla shell, ciò che quel comando riceve come argomenti è una matrice di stringhe con terminazione null. Tali stringhe possono contenere assolutamente qualsiasi carattere non nullo.

Ma quando la shell sta costruendo quella matrice di stringhe da una riga di comando, interpreta alcuni caratteri appositamente; questo è progettato per rendere i comandi più facili (anzi, possibili) da digitare. Ad esempio, gli spazi indicano normalmente il limite tra le stringhe nell'array; per questo motivo, i singoli argomenti sono talvolta chiamati "parole". Ma un argomento può tuttavia contenere degli spazi; hai solo bisogno di un modo per dire alla shell che è quello che vuoi.

Puoi usare una barra rovesciata di fronte a qualsiasi personaggio (incluso lo spazio o un'altra barra rovesciata) per dire alla shell di trattare quel personaggio alla lettera. Ma mentre puoi fare qualcosa del genere:

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

... può diventare noioso. Quindi la shell offre un'alternativa: virgolette. Questi sono disponibili in due varietà principali.

Le virgolette doppie sono chiamate "virgolette di raggruppamento". Impediscono l'espansione di caratteri jolly e alias, ma principalmente servono per includere spazi in una parola. Altre cose come l'espansione dei parametri e dei comandi (il genere di cose segnalato da a $) si verificano ancora. E ovviamente se vuoi una doppia virgoletta letterale all'interno di virgolette doppie, devi sostituirla:

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

Le virgolette singole sono più draconiane. Tutto tra loro viene preso alla lettera, comprese le barre rovesciate. Non c'è assolutamente modo di ottenere una virgoletta singola letterale all'interno di virgolette singole.

Fortunatamente, le virgolette nella shell non sono delimitatori di parole ; da soli, non terminano una parola. Puoi entrare e uscire dalle virgolette, anche tra diversi tipi di virgolette, all'interno della stessa parola per ottenere il risultato desiderato:

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

Quindi è più facile: molte meno barre rovesciate, anche se la sequenza di virgolette singole, virgolette-letterali, virgolette singole, richiede un po 'di tempo per abituarsi.

Le shell moderne hanno aggiunto un altro stile di quotazione non specificato dallo standard POSIX, in cui il segno di virgolette singolo iniziale è preceduto da un segno di dollaro. Le stringhe in modo citati seguono le convenzioni simili a stringhe letterali nel linguaggio di programmazione ANSI C, e quindi sono a volte chiamati "stringhe ANSI" e la $'... 'coppia di "citazioni" ANSI. All'interno di tali stringhe, i consigli di cui sopra sui contraccolpi che vengono presi alla lettera non si applicano più. Invece, diventano di nuovo speciali - non solo puoi includere una virgoletta singola letterale o una barra rovesciata anteponendole a una barra rovesciata, ma la shell espande anche le escape del carattere ANSI C (come \nper una nuova riga, \tper la scheda e \xHHper il carattere con codice esadecimaleHH). Altrimenti, tuttavia, si comportano come stringhe tra virgolette singole: non viene effettuata la sostituzione di alcun parametro o comando:

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

La cosa importante da notare è che la singola stringa ricevuta come argomento per il echocomando è esattamente la stessa in tutti questi esempi. Dopo che la shell ha terminato l'analisi di una riga di comando, non è possibile eseguire il comando per dire cosa è stato citato. Anche se lo volesse.


2
Sì. Ora capisco. Funziona perfettamente. Grazie mille per il vostro aiuto!
Rachit,

6
Questa risposta è stata fantastica.
Vinícius Ferrão,

1
Il $'string'formato POSIX è conforme? Inoltre, c'è un nome per questo?
Wildcard

2
@Wildcard $'string'è un'estensione non POSIX, che ho visto denominata "stringhe ANSI"; Ho incorporato questi fatti nella risposta. Tali stringhe funzionano nella maggior parte delle moderne shell compatibili con Bourne: bash, dash, ksh (sia AT&T che PD), e zsh le supportano tutte.
Mark Reed,


3

Di seguito è ciò che ha funzionato per me -

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"

2
Spero che TBL_HDFS_DIR_PATH non sia l'input fornito dall'utente tra l'altro, questo consentirebbe una bella iniezione sql ...
domenukk,

1
Inserire QUOTEuna variabile separata è completamente superfluo; le virgolette singole all'interno delle virgolette doppie sono solo un carattere regolare. (Inoltre, non utilizzare le maiuscole per le variabili private.)
tripleee

2

EDIT: (Secondo i commenti in questione :)

Ho esaminato questo da allora. Ho avuto la fortuna di avere un repo in giro. Tuttavia non mi è chiaro se è necessario racchiudere forzatamente i comandi tra virgolette singole. Ho esaminato la sintassi del repository e non penso che sia necessario. Potresti usare le virgolette doppie attorno al tuo comando, quindi utilizzare le virgolette singole e doppie di cui hai bisogno all'interno, a patto di scappare da quelle doppie.


Grazie Kevin per il tuo aiuto.
Rachit,

2

usa solo printf

invece di

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

usa printf per sostituire il token variabile con la variabile espansa.

Per esempio:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")

Questo è rotto; printfin realtà non ti compra nulla qui e non riuscire a citare la sostituzione del comando provoca nuovi problemi di quotazione.
triplo

0

Le variabili possono contenere virgolette singole.

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

repo forall -c $myvar

Possono, ma questo non è il modo giusto per farlo - dovresti comunque mettere "$myvar"tra virgolette.
Tripleee

-2

Per te funziona?

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

8
Lo fa per te? Questa domanda ha cinque anni e ha già alcune risposte, anche accettate ...
Nico Haase,
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.