Často končím vydáváním složitých příkazů přes ssh; tyto příkazy zahrnují propojení na awk nebo perl jednořádkové řádky a ve výsledku obsahují jednoduché uvozovky a znaky $. Nebyl jsem schopen vymyslet pevné a rychlé pravidlo, jak správně citovat, ani jsem pro něj nenašel dobrou referenci. Zvažte například následující:
# what I'd run locally:
CMD='pgrep -fl java | grep -i datanode | awk '{print $1}'
# this works with ssh $host "$CMD":
CMD='pgrep -fl java | grep -i datanode | awk '"'"'{print $1}'"'"
(Všimněte si dalších uvozovek v příkazu awk.)
Ale jak s tím mám pracovat, např. ssh $host "sudo su user -c '$CMD'"
? Existuje obecný recept na správu nabídek v takových scénářích?...
Přijatá odpověď:
Práce s více úrovněmi citování (ve skutečnosti více úrovní analýzy/interpretace) se může zkomplikovat. Pomáhá mít na paměti několik věcí:
- Každá „úroveň citování“ může potenciálně zahrnovat jiný jazyk.
- Pravidla pro citování se liší podle jazyka.
- Při práci s více než jednou nebo dvěma vnořenými úrovněmi je obvykle nejsnazší pracovat „odspodu nahoru“ (tj. od nejvnitřnějšího k nejvzdálenějšímu).
Úrovně cenových nabídek
Podívejme se na vaše ukázkové příkazy.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Váš první ukázkový příkaz (výše) používá čtyři jazyky:váš shell, regex v pgrep , regulární výraz v grep (který se může lišit od jazyka regulárních výrazů v pgrep ) a awk . Existují dvě úrovně interpretace:shell a jedna úroveň po shellu pro každý ze zapojených příkazů. Existuje pouze jedna explicitní úroveň citování (skořápkové citace do awk ).
ssh host …
Dále jste přidali úroveň ssh na vrchu. Toto je v podstatě další úroveň prostředí:ssh neinterpretuje samotný příkaz, předá jej shellu na vzdáleném konci (např. pomocí sh -c …
) a tento shell interpretuje řetězec.
ssh host "sudo su user -c …"
Pak jste se zeptali na přidání další úrovně shellu doprostřed pomocí su (přes sudo , který neinterpretuje své argumenty příkazu, takže jej můžeme ignorovat). V tuto chvíli probíhají tři úrovně vnořování (awk → shell, shell → shell (ssh ), shell → shell (su user -c ), takže doporučuji použít přístup „zdola nahoru“. Předpokládám, že vaše mušle jsou kompatibilní s Bourne (např. sh , popel , pomlčka , ksh , bash , zsh , atd.). Nějaký jiný druh skořápky (ryba , rc , atd.) může vyžadovat jinou syntaxi, ale metoda stále platí.
Dolů, nahoru
- Formulujte řetězec, který chcete reprezentovat, na nejvnitřnější úrovni.
- Vyberte mechanismus citování z repertoáru citací v dalším nejvyšším jazyce.
- Uveďte požadovaný řetězec podle vámi zvoleného mechanismu uvozovek.
- Často existuje mnoho variant, jak použít který mechanismus citování. Dělat to ručně je většinou otázkou cviku a zkušeností. Když to děláte programově, je obvykle nejlepší vybrat to, co je nejsnazší (obvykle to „nejdoslovnější“ (nejméně úniků)).
- Volitelně použijte výsledný řetězec v uvozovkách s dalším kódem.
- Pokud jste ještě nedosáhli požadované úrovně citování/interpretace, vezměte výsledný řetězec v uvozovkách (plus případný přidaný kód) a použijte jej jako počáteční řetězec v kroku 2.
Sémantika citací se liší
Zde je třeba mít na paměti, že každý jazyk (úroveň citování) může udělit mírně odlišnou sémantiku (nebo dokonce drasticky odlišnou sémantiku) stejnému znaku v uvozovkách.
Většina jazyků má mechanismus „doslovných“ citací, ale liší se přesně tím, jak doslovné jsou. Jediná uvozovka v Bourneovských mušlích je ve skutečnosti doslovná (což znamená, že ji nemůžete použít k citaci samotného znaku uvozovky). Jiné jazyky (Perl, Ruby) jsou méně doslovné v tom, že některé interpretují sekvence zpětného lomítka uvnitř oblastí v jednoduchých uvozovkách nedoslovně (konkrétně \
a '
výsledkem je a
'
, ale ostatní sekvence zpětného lomítka jsou ve skutečnosti doslovné).
Budete si muset přečíst dokumentaci pro každý z vašich jazyků, abyste pochopili jeho pravidla pro citování a celkovou syntaxi.
Váš příklad
Nejvnitřnější úrovní vašeho příkladu je awk program.
{print $1}
Chystáte se to vložit do příkazového řádku shellu:
pgrep -fl java | grep -i datanode | awk …
Musíme chránit (minimálně) prostor a $
v awk program. Jasnou volbou je použít v shellu kolem celého programu jednu uvozovku.
'{print $1}'
Existují však i další možnosti:
{print $1}
přímo opustit mezeru a$
{print' $'1}
jednoduché uvozovky pouze mezeru a$
"{print $1}"
dvakrát uvozujte celé a uvozujte$
{print" $"1}
uvozujte pouze mezeru a$
To může trochu ohýbat pravidla (bez kódování$
na konci řetězce v dvojitých uvozovkách je doslovný), ale zdá se, že to funguje ve většině shellů.
Pokud by program používal čárku mezi otevřenou a zavřenou složenou závorkou, museli bychom také čárku nebo složenou závorku uvést v uvozovkách, aby se předešlo „rozšíření složené závorky“ v některých shellech.
Vybíráme '{print $1}'
a vložte jej do zbytku „kódu“ shellu:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Dále jste to chtěli spustit pomocí su a sudo .
sudo su user -c …
su user -c …
je stejně jako some-shell -c …
(kromě běhu pod nějakým jiným UID), takže su jen přidává další úroveň shellu. sudo neinterpretuje své argumenty, takže nepřidává žádné úrovně uvozovek.
Potřebujeme další úroveň shellu pro náš příkazový řetězec. Můžeme znovu vybrat jednoduché nabídky, ale musíme věnovat zvláštní péči stávajícím jednoduchým cenovým nabídkám. Obvyklý způsob vypadá takto:
'pgrep -fl java | grep -i datanode | awk '''{print $1}''
Jsou zde čtyři řetězce, které bude shell interpretovat a zřetězit:první řetězec v jednoduchých uvozovkách (pgrep … awk
), jednoduchá uvozovka, awk s jednoduchými uvozovkami program, další jednoduchá uvozovka.
Existuje samozřejmě mnoho alternativ:
pgrep -fl java | grep -i datanode | awk '{print $1}
uniknout všemu důležitémupgrep -fl java|grep -i datanode|awk '{print$1}
totéž, ale bez nadbytečných mezer (i v awk program!)"pgrep -fl java | grep -i datanode | awk '{print $1}'"
dvojité uvozovky celé, escapujte$
'pgrep -fl java | grep -i datanode | awk '"'"'{print $1}'"'"
vaše variace; o něco delší než obvyklým způsobem kvůli použití dvojitých uvozovek (dva znaky) namísto escape (jeden znak)
Použití různých citací v první úrovni umožňuje další varianty na této úrovni:
'pgrep -fl java | grep -i datanode | awk "{print $1}"'
'pgrep -fl java | grep -i datanode | awk {print $1}'
Vložení první varianty do sudo /*su* příkazový řádek zadejte toto:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '''{print $1}''
Stejný řetězec můžete použít v jakémkoli jiném kontextu na úrovni prostředí (např. ssh host …
).
Dále jste přidali úroveň ssh na vrchu. Toto je v podstatě další úroveň prostředí:ssh neinterpretuje samotný příkaz, ale předá jej shellu na vzdáleném konci (např. pomocí sh -c …
) a tento shell interpretuje řetězec.
ssh host …
Postup je stejný:vezměte řetězec, vyberte metodu citování, použijte ji, vložte ji.
Opět použití jednoduchých uvozovek:
'sudo su user -c '''pgrep -fl java | grep -i datanode | awk ''\'''{print $1}''\'
Nyní existuje jedenáct řetězců, které jsou interpretovány a zřetězeny:'sudo su user -c '
, jednoduchá uvozovka, 'pgrep … awk '
, jednoduchá uvozovka, zpětné lomítko, dvě uvozovky, jednoduchá uvozovka awk program, jednoduchá uvozovka, zpětné lomítko a poslední uvozovka.
Finální podoba vypadá takto:
ssh host 'sudo su user -c '''pgrep -fl java | grep -i datanode | awk ''\'''{print $1}''\'
Ruční psaní je trochu nepraktické, ale doslovná povaha jednoduchých uvozovek shellu usnadňuje automatizaci mírné variace:
#!/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 '%sn' "$*" | 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")"