Gestire più livelli di quotazione (in realtà, più livelli di analisi / interpretazione) può diventare complicato. Aiuta a tenere a mente alcune cose:
- Ogni "livello di quotazione" può potenzialmente coinvolgere una lingua diversa.
- Le regole di citazione variano in base alla lingua.
- Quando si ha a che fare con più di uno o due livelli nidificati, di solito è più facile lavorare "dal basso verso l'alto" (cioè dal più interno al più esterno).
Livelli di quotazione
Diamo un'occhiata ai tuoi comandi di esempio.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Il tuo primo comando di esempio (sopra) usa quattro lingue: la tua shell, il regex in pgrep , il regex in grep (che potrebbe essere diverso dal linguaggio regex in pgrep ) e awk . Esistono due livelli di interpretazione: la shell e un livello dopo la shell per ciascuno dei comandi coinvolti. C'è solo un livello esplicito di quotazione (shell quoting in awk ).
ssh host …
Successivamente hai aggiunto un livello di ssh in cima. Questo è effettivamente un altro livello di shell: ssh non interpreta il comando stesso, lo passa a una shell sull'estremità remota (via (es.) sh -c …
) E quella shell interpreta la stringa.
ssh host "sudo su user -c …"
Quindi hai chiesto di aggiungere un altro livello di shell nel mezzo usando su (tramite sudo , che non interpreta i suoi argomenti di comando, quindi possiamo ignorarlo). A questo punto, hai tre livelli di annidamento in corso ( awk → shell, shell → shell ( ssh ), shell → shell ( su user -c ), quindi ti consiglio di usare l'approccio "bottom, up". le tue conchiglie sono compatibili con Bourne (es. sh , ash , dash , ksh , bash , zsh , ecc.) Alcuni altri tipi di shell ( fish , rc, ecc.) potrebbe richiedere una sintassi diversa, ma il metodo è ancora valido.
Dal basso verso l'alto
- Formulare la stringa che si desidera rappresentare al livello più interno.
- Seleziona un meccanismo di quotazione dal repertorio di citazioni della lingua successiva più alta.
- Cita la stringa desiderata in base al meccanismo di quotazione selezionato.
- Esistono spesso molte varianti su come applicare quale meccanismo di quotazione. Farlo a mano è di solito una questione di pratica ed esperienza. Quando lo si fa in modo programmatico, di solito è meglio scegliere il più facile da ottenere (di solito il "più letterale" (il minor numero di fughe)).
- Facoltativamente, utilizzare la stringa tra virgolette risultante con codice aggiuntivo.
- Se non hai ancora raggiunto il livello desiderato di quotazione / interpretazione, prendi la stringa tra virgolette risultante (più qualsiasi codice aggiunto) e usala come stringa iniziale nel passaggio 2.
Citando la semantica varia
La cosa da tenere a mente qui è che ogni lingua (livello di quotazione) può dare una semantica leggermente diversa (o anche una semantica drasticamente diversa) allo stesso carattere di citazione.
La maggior parte delle lingue ha un meccanismo di quotazione "letterale", ma varia esattamente in quanto letterale. La singola citazione di conchiglie simili a Bourne è in realtà letterale (il che significa che non è possibile utilizzarla per citare un singolo carattere di citazione stesso). Altre lingue (Perl, Ruby) sono meno letterali in quanto interpretano non letteralmente alcune sequenze di barre rovesciate all'interno di singole regioni tra virgolette (in particolare, \\
e \'
risultano in \
e '
, ma altre sequenze di barre rovesciate sono in realtà letterali).
Dovrai leggere la documentazione per ciascuna delle tue lingue per comprendere le regole di quotazione e la sintassi generale.
Il tuo esempio
Il livello più interno del tuo esempio è un programma awk .
{print $1}
Lo incorporerai in una riga di comando della shell:
pgrep -fl java | grep -i datanode | awk …
Abbiamo bisogno di proteggere (come minimo) lo spazio e l' $
nel awk programma. La scelta ovvia è quella di utilizzare la virgoletta singola nella shell attorno all'intero programma.
Ci sono altre scelte però:
{print\ \$1}
sfuggire direttamente allo spazio e $
{print' $'1}
citazione unica solo lo spazio e $
"{print \$1}"
doppia citazione del tutto e sfuggire al $
{print" $"1}
virgolette solo lo spazio e $
questo potrebbe piegare un po 'le regole (senza caratteri di escape $
alla fine di una stringa tra virgolette doppie è letterale), ma sembra funzionare nella maggior parte delle shell.
Se il programma utilizzava una virgola tra parentesi graffe aperte e chiuse, avremmo anche bisogno di citare o sfuggire alla virgola o alle parentesi graffe per evitare l'espansione delle parentesi graffe in alcune shell.
Lo selezioniamo '{print $1}'
e lo incorporiamo nel resto del "codice" della shell:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Successivamente, si voleva eseguire questo tramite su e sudo .
sudo su user -c …
su user -c …
è come some-shell -c …
(tranne che in esecuzione con un altro UID), quindi su aggiunge solo un altro livello di shell. sudo non interpreta i suoi argomenti, quindi non aggiunge alcun livello di quotazione.
Abbiamo bisogno di un altro livello di shell per la nostra stringa di comando. Possiamo scegliere di nuovo le virgolette singole, ma dobbiamo dare una gestione speciale alle virgolette singole esistenti. Il solito modo è simile al seguente:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Qui ci sono quattro stringhe che la shell interpreterà e concatenerà: la prima stringa tra virgolette singole ( pgrep … awk
), una virgoletta singola con escape, il programma awk con virgolette singole , un'altra virgoletta singola con escape.
Esistono ovviamente molte alternative:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
sfuggire a tutto ciò che è importante
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
lo stesso, ma senza spazi bianchi superflui (anche nel programma awk !)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
doppia citazione l'intera cosa, sfuggire al $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
la tua variazione; un po 'più lungo del solito a causa dell'utilizzo di virgolette doppie (due caratteri) invece di escape (un carattere)
L'uso di quotazioni diverse nel primo livello consente altre variazioni a questo livello:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
Incorporando la prima variante nella riga di comando sudo / * su * si ottiene questo:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
È possibile utilizzare la stessa stringa in qualsiasi altro contesto a livello di shell singola (ad es ssh host …
.).
Successivamente, hai aggiunto un livello di ssh in cima. Questo è effettivamente un altro livello di shell: ssh non interpreta il comando stesso, ma lo passa a una shell sull'estremità remota (via (es.) sh -c …
) E quella shell interpreta la stringa.
ssh host …
Il processo è lo stesso: prendi la stringa, scegli un metodo di quotazione, usalo, incorporalo.
Usando nuovamente le virgolette singole:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Ora ci sono undici le stringhe che vengono interpretati e concatenati: 'sudo su user -c '
, sfuggito apostrofo, 'pgrep … awk '
, sfuggito apostrofo, barra inversa sfuggito, due scapparono virgolette singole, il singolo citato awk programma, un sfuggito apostrofo, una barra rovesciata fuggito, e una finale sfuggito sola offerta .
Il modulo finale è simile al seguente:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Questo è un po 'ingombrante da digitare a mano, ma la natura letterale della virgoletta singola della shell rende facile automatizzare una leggera variazione:
#!/bin/sh
sq() { # single quote for Bourne shell evaluation
# Change ' to '\'' and wrap in single quotes.
# If original starts/ends with a single quote, creates useless
# (but harmless) '' at beginning/end of result.
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }
ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"
ssh host "$(sq "$s2")"