Radzenie sobie z wieloma poziomami cytowania (tak naprawdę, wieloma poziomami parsowania / interpretacji) może być skomplikowane. Pomaga pamiętać o kilku rzeczach:
- Każdy „poziom cytowania” może potencjalnie obejmować inny język.
- Zasady cytowania różnią się w zależności od języka.
- W przypadku więcej niż jednego lub dwóch zagnieżdżonych poziomów zwykle najłatwiej jest pracować „od dołu do góry” (tj. Od środka do najbardziej na zewnątrz).
Poziomy cytowania
Spójrzmy na twoje przykładowe polecenia.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Twoje pierwsze przykładowe polecenie (powyżej) używa czterech języków: twojej powłoki, wyrażenia regularnego w pgrep , wyrażenia regularnego w grep (które mogą być inne niż język wyrażenia regularnego w pgrep ) i awk . Istnieją dwa poziomy interpretacji: powłoka i jeden poziom za powłoką dla każdego z zaangażowanych poleceń. Istnieje tylko jeden wyraźny poziom cytowania (cytowanie powłoki w awk ).
ssh host …
Następnie dodałeś poziom ssh na górze. Jest to faktycznie kolejny poziom powłoki: ssh nie interpretuje samego polecenia, przekazuje je powłoce na zdalnym końcu (przez (np.) sh -c …
) I ta powłoka interpretuje ciąg znaków.
ssh host "sudo su user -c …"
Następnie zapytałeś o dodanie kolejnego poziomu powłoki za pomocą su (via sudo , który nie interpretuje argumentów poleceń, więc możemy go zignorować). W tym momencie masz trzy poziomy zagnieżdżania ( awk → powłoka, powłoka → powłoka ( ssh ), powłoka → powłoka ( su user -c ), więc radzę stosować podejście „od dołu do góry”. twoje muszle są kompatybilne z Bourne (np. sh , ash , dash , ksh , bash , zsh itp.) Niektóre inne rodzaje muszli ( ryby , rcitp.) może wymagać innej składni, ale metoda nadal obowiązuje.
Dół, góra
- Sformułuj ciąg, który chcesz reprezentować na najbardziej wewnętrznym poziomie.
- Wybierz mechanizm cytowania z repertuaru cytowań następnego najwyższego języka.
- Podaj żądany ciąg zgodnie z wybranym mechanizmem cytowania.
- Często istnieje wiele wariantów zastosowania mechanizmu cytowania. Robienie tego ręcznie jest zwykle kwestią praktyki i doświadczenia. Robiąc to programowo, zazwyczaj najlepiej jest wybrać najłatwiejszy do zrobienia (zwykle „najbardziej dosłowny” (najmniej ucieczek)).
- Opcjonalnie możesz użyć wynikowego cudzysłowu z dodatkowym kodem.
- Jeśli nie osiągnąłeś jeszcze pożądanego poziomu cytowania / interpretacji, weź otrzymany ciąg cytowany (plus dowolny dodany kod) i użyj go jako łańcucha początkowego w kroku 2.
Cytując Semantykę Zmieniaj się
Należy pamiętać, że każdy język (poziom cytowania) może nadawać nieco inną semantykę (lub nawet drastycznie inną semantykę) temu samemu znakowi cytowania.
Większość języków ma „dosłowny” mechanizm cytowania, ale różnią się dokładnie pod względem dosłowności. Pojedynczy cytat z powłok podobnych do Bourne'a jest w rzeczywistości dosłowny (co oznacza, że nie można go używać do cytowania samego znaku cytowania). Inne języki (Perl, Ruby) są mniej dosłowne w tym, że interpretują pewne sekwencje backslash w pojedynczych regionach cytowane dosłownie non-(konkretnie, \\
a \'
wynik w \
i '
, ale inne sekwencje ukośnik odwrotny to rzeczywiście dosłowny).
Będziesz musiał przeczytać dokumentację dla każdego języka, aby zrozumieć zasady cytowania i ogólną składnię.
Twój przykład
Najbardziej wewnętrzny poziom twojego przykładu to program awk .
{print $1}
Zamieścisz to w linii poleceń powłoki:
pgrep -fl java | grep -i datanode | awk …
Musimy chronić (przynajmniej) w przestrzeni i $
w awk programie. Oczywistym wyborem jest użycie pojedynczego cudzysłowu w powłoce wokół całego programu.
Istnieją jednak inne opcje:
{print\ \$1}
bezpośrednio uciec z przestrzeni i $
{print' $'1}
pojedynczy cytat tylko przestrzeń i $
"{print \$1}"
podwójnie zacytuj całość i uciec $
{print" $"1}
podwójny cudzysłów tylko spacja i $
to może nieco wyginać reguły (nieosłonięte $
na końcu łańcucha podwójnego cudzysłowu jest dosłowne), ale wydaje się, że działa w większości powłok.
Gdyby program używał przecinka między otwartymi i zamkniętymi nawiasami klamrowymi, musielibyśmy również zacytować lub uciec albo przecinek, albo nawiasy klamrowe, aby uniknąć „rozszerzenia nawiasów klamrowych” w niektórych powłokach.
Wybieramy '{print $1}'
i osadzamy go w pozostałej części „kodu” powłoki:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Następnie chciałeś uruchomić to za pomocą su i sudo .
sudo su user -c …
su user -c …
jest podobny some-shell -c …
(oprócz działania pod innym identyfikatorem UID), więc su po prostu dodaje kolejny poziom powłoki. sudo nie interpretuje swoich argumentów, więc nie dodaje żadnych poziomów cytowania.
Potrzebujemy innego poziomu powłoki dla naszego ciągu poleceń. Możemy ponownie wybrać pojedyncze cytaty, ale musimy specjalnie traktować istniejące pojedyncze cytaty. Zwykły sposób wygląda następująco:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Są tutaj cztery ciągi, które powłoka będzie interpretować i konkatenować: pierwszy ciąg pojedynczego cudzysłowu ( pgrep … awk
), pojedynczy cytat ze znakiem ucieczki, program awk z pojedynczym cudzysłowem , inny pojedynczy cytat ze znakiem ucieczki.
Istnieje oczywiście wiele alternatyw:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
uciec od wszystkiego, co ważne
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
to samo, ale bez zbędnych białych znaków (nawet w programie awk !)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
podwójnie zacytuj całość, ucieknij $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
twoja odmiana; nieco dłuższy niż zwykle ze względu na użycie podwójnych cudzysłowów (dwa znaki) zamiast znaków ucieczki (jeden znak)
Użycie różnych cytatów na pierwszym poziomie pozwala na inne warianty na tym poziomie:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
Osadzenie pierwszej odmiany w wierszu polecenia sudo / * su * daje:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Możesz użyć tego samego łańcucha w dowolnym innym kontekście pojedynczego poziomu powłoki (np ssh host …
.).
Następnie dodałeś poziom ssh na górze. Jest to w rzeczywistości kolejny poziom powłoki: ssh nie interpretuje samego polecenia, ale przekazuje je powłoce na zdalnym końcu (przez (np.) sh -c …
) I ta powłoka interpretuje ciąg znaków.
ssh host …
Proces jest taki sam: weź ciąg, wybierz metodę cytowania, użyj go, umieść go.
Ponowne użycie pojedynczych cudzysłowów:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Teraz jest jedenaście ciągów, które są interpretowane i łączone:, 'sudo su user -c '
uniknął pojedynczego cytatu, 'pgrep … awk '
uniknął pojedynczego cytatu, uniknął odwrotnego ukośnika, dwóch unikniętych pojedynczych cytatów, pojedynczego cytowanego programu awk , unikniętego pojedynczego cytatu, unikniętego odwrotnego ukośnika i ostatniego unikniętego pojedynczego cytatu .
Ostateczna forma wygląda następująco:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Pisanie ręczne jest trochę niewygodne, ale dosłowny charakter pojedynczego cytowania powłoki ułatwia automatyzację niewielkich zmian:
#!/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")"
Zobacz odpowiedź Chrisa Johnsena, aby uzyskać jasne, dogłębne wyjaśnienie z ogólnym rozwiązaniem. Dam kilka dodatkowych wskazówek, które pomogą w niektórych typowych okolicznościach.
Pojedyncze cytaty unikają wszystkiego oprócz jednego cytatu. Jeśli więc wiesz, że wartość zmiennej nie zawiera pojedynczego cudzysłowu, możesz bezpiecznie interpolować ją między pojedynczymi cudzysłowami w skrypcie powłoki.
Jeśli twoją lokalną powłoką jest ksh93 lub zsh, możesz poradzić sobie z pojedynczymi cudzysłowami w zmiennej, przepisując je do
'\''
. (Chociaż bash ma również${foo//pattern/replacement}
konstrukcję, jego obsługa pojedynczych cudzysłowów nie ma dla mnie sensu).Kolejną wskazówką, aby uniknąć konieczności radzenia sobie z zagnieżdżonym cytowaniem, jest przepuszczanie ciągów przez zmienne środowiskowe w jak największym stopniu. Ssh i sudo mają tendencję do upuszczania większości zmiennych środowiskowych, ale często są skonfigurowane do przepuszczania
LC_*
, ponieważ są one zwykle bardzo ważne dla użyteczności (zawierają informacje o lokalizacji) i rzadko są uważane za wrażliwe na bezpieczeństwo.Tutaj, ponieważ
LC_CMD
zawiera fragment powłoki, musi być dostarczony dosłownie do najbardziej wewnętrznej powłoki. Dlatego zmienna jest rozszerzana przez powłokę bezpośrednio powyżej. Powłoka najbardziej wewnętrzna-jedna widzi"$LC_CMD"
, a powłoka najbardziej wewnętrzna widzi polecenia.Podobna metoda jest przydatna do przekazywania danych do narzędzia do przetwarzania tekstu. Jeśli użyjesz interpolacji powłoki, narzędzie potraktuje wartość zmiennej jako polecenie, np.
sed "s/$pattern/$replacement/"
Nie będzie działać, jeśli zmienne zawierają/
. Więc użyj awk (nie sed) i jego-v
opcji lubENVIRON
tablicy, aby przekazać dane z powłoki (jeśli przejdzieszENVIRON
, pamiętaj, aby wyeksportować zmienne).źródło
Jak Chris bardzo dobrze opisuje , masz tutaj kilka cytowanych poziomów pośrednich; poinstruować lokalny
shell
pouczać pilotashell
viassh
że powinna pouczaćsudo
pouczyćsu
się instruować pilotashell
uruchomić rurociągpgrep -fl java | grep -i datanode | awk '{print $1}'
jakouser
. Tego rodzaju polecenie wymaga wielu żmudnych działań\'"quote quoting"\'
.Jeśli posłuchasz mojej rady, porzucisz wszystkie bzdury i zrobisz:
WIĘCEJ:
Sprawdź moją (być może zbyt długą) odpowiedź na ścieżki rurociągów z różnymi rodzajami cytatów dotyczących zastępowania cięciami, w których omawiam niektóre teorie leżące u podstaw tego, dlaczego to działa.
-Mikrofon
źródło
<<
po prostu zastępuje pierwszą. Czy powinno być gdzieś napisane „tylko zsh”, czy może coś przeoczyłem? (Sprytna sztuczka, aby mieć heredok, który częściowo podlega lokalnej ekspansji)Co powiesz na użycie więcej podwójnych cudzysłowów?
W takim razie
ssh $host $CMD
powinieneś dobrze pracować z tym:CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"
Teraz do bardziej złożonego -
ssh $host "sudo su user -c \"$CMD\""
. Chyba wszystko co musisz zrobić, to uciec znaków wrażliwe naCMD
:$
,\
i"
. Więc chciałbym spróbować i zobaczyć, czy to działa:echo $CMD | sed -e 's/[$\\"]/\\\1/g'
.Jeśli to wygląda OK, zawiń echo + sed w funkcję powłoki i dobrze jest zacząć
ssh $host "sudo su user -c \"$(escape_my_var $CMD)\""
.źródło