ssh
ma irytującą funkcję polegającą na tym, że po uruchomieniu:
ssh user@host cmd and "here's" "one arg"
Zamiast uruchamiać to cmd
z włączonymi argumentami host
, łączy to cmd
i argumenty ze spacjami i uruchamia powłokę, host
aby zinterpretować otrzymany ciąg (chyba dlatego jest wywoływany, ssh
a nie sexec
).
Co gorsza, nie wiesz, która powłoka będzie używana do interpretacji tego ciągu, ponieważ jest to powłoka logowania, user
której nie można nawet zagwarantować, że jest Bourne, ponieważ wciąż są ludzie używający tcsh
ich powłoki logowania i fish
rośnie.
Czy jest na to jakiś sposób?
Załóżmy, że mam polecenie jako lista argumentów przechowywanych w bash
tablicy, z których każdy może zawierać dowolną sekwencję bajtów non-null, czy jest jakiś sposób na to realizowane na host
jak user
w spójny sposób, niezależnie od powłoki logowania, że user
na host
(zakładamy, że jest jedną z głównych rodzin powłok uniksowych: Bourne, csh, rc / es, fish)?
Innym rozsądne założenie, że powinienem być w stanie zrobić to, że nie będzie sh
komenda na host
dostępne w $PATH
to Bourne-kompatybilne.
Przykład:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Mogę uruchomić go lokalnie za pomocą ksh
/ zsh
/ bash
/ yash
as:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
lub
env "${cmd[@]}"
lub
xterm -hold -e "${cmd[@]}"
...
Jak bym go uruchomić na host
jak user
powyżej ssh
?
ssh user@host "${cmd[@]}"
oczywiście nie zadziała.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
działałoby tylko wtedy, gdy powłoka logowania zdalnego użytkownika była taka sama jak powłoka lokalna (lub rozumie cytowanie w taki sam sposób, jak printf %q
w powłoce lokalnej ją produkuje) i działa w tych samych ustawieniach narodowych.
źródło
cmd
argument/bin/sh -c
byłby taki, że w 99% przypadków otrzymalibyśmy powłokę posiksową, prawda? Oczywiście ucieczka od znaków specjalnych jest w ten sposób nieco bardziej bolesna, ale czy rozwiązałoby to początkowy problem?ssh host sh -c 'some cmd'
tak samo, jakssh host 'sh -c some cmd'
ma powłokę logowania użytkownika zdalnego, interpretuj tensh -c some cmd
wiersz poleceń. Musimy napisać polecenia w poprawnej składni tego płaszcza (i nie wiem, który to jest), tak abysh
być nazywany tam z-c
isome cmd
argumenty.sh -c 'some cmd'
isome cmd
są interpretowane tak samo we wszystkich tych powłokach. Co teraz, jeśli chcę uruchomićecho \'
wiersz poleceń Bourne na zdalnym hoście?echo command-string | ssh ... /bin/sh
to jedno rozwiązanie, które podałem w mojej odpowiedzi, ale oznacza to, że nie możesz podać danych na standardowe wyjście tej zdalnej komendy.cmd
takcmd=(echo "foo bar")
, przekazany wiersz poleceń powłokissh
powinien być czymś w rodzaju wiersza polecenia „echo” foo bar „. The *first* space (the one before
echo) is superflous, but doen't harm. The other one (the ones before
” foo bar ”) is needed. With
„% q ”, we'd pass a
„ echo ”„ pasek foo ” .Odpowiedzi:
Nie sądzę, aby jakakolwiek implementacja
ssh
miała natywny sposób przekazywania poleceń z klienta na serwer bez angażowania powłoki.Teraz może być łatwiej, jeśli powiesz zdalnej powłoce, aby uruchomiła tylko określony interpreter (na przykład
sh
, dla którego znamy oczekiwaną składnię) i poda kod do wykonania w inny sposób.Tym innym środkiem może być na przykład standardowe wejście lub zmienna środowiskowa .
Gdy żadnego z nich nie można użyć, poniżej proponuję hacky trzecie rozwiązanie.
Używanie standardowego wejścia
Jeśli nie musisz podawać żadnych danych do polecenia zdalnego, jest to najłatwiejsze rozwiązanie.
Jeśli wiesz, że na zdalnym hoście znajduje się
xargs
polecenie obsługujące tę-0
opcję, a polecenie to nie jest zbyt duże, możesz:Ta
xargs -0 env --
linia poleceń jest interpretowana tak samo dla wszystkich rodzin powłok.xargs
odczytuje listę argumentów ograniczoną przez zero na stdin i przekazuje je jako argumenty doenv
. Zakłada się, że pierwszy argument (nazwa polecenia) nie zawiera=
znaków.Lub możesz użyć
sh
na zdalnym hoście po zacytowaniu każdego elementu przy użyciush
składni cytowania.Korzystanie ze zmiennych środowiskowych
Teraz, jeśli musisz podać pewne dane od klienta do standardowego polecenia zdalnego, powyższe rozwiązanie nie będzie działać.
Niektóre
ssh
wdrożenia serwera umożliwiają jednak przekazywanie dowolnych zmiennych środowiskowych z klienta na serwer. Na przykład wiele wdrożeń openssh w systemach opartych na Debianie umożliwia przekazywanie zmiennych, których nazwa zaczyna się odLC_
.W takich przypadkach możesz mieć
LC_CODE
na przykład zmienną zawierającą kod dzielonysh
jak wyżej i uruchomićsh -c 'eval "$LC_CODE"'
na zdalnym hoście po tym, jak kazałeś klientowi przekazać tę zmienną (ponownie, to wiersz poleceń, który jest interpretowany tak samo w każdej powłoce):Budowanie wiersza poleceń zgodnego ze wszystkimi rodzinami powłok
Jeśli żadna z powyższych opcji nie jest akceptowalna (ponieważ potrzebujesz stdin, a sshd nie akceptuje żadnej zmiennej lub potrzebujesz ogólnego rozwiązania), musisz przygotować wiersz poleceń dla zdalnego hosta, który jest kompatybilny ze wszystkimi obsługiwane powłoki.
Jest to szczególnie trudne, ponieważ wszystkie te powłoki (Bourne, csh, rc, es, fish) mają swoją inną składnię, a w szczególności różne mechanizmy cytowania, a niektóre z nich mają ograniczenia, które trudno jest obejść.
Oto rozwiązanie, które wymyśliłem, opisuję je poniżej:
To jest
perl
skrypt opakowaniassh
. Nazywam tosexec
. Nazywasz to tak:więc w twoim przykładzie:
Opakowanie zamienia
cmd and its args
się w wiersz poleceń, który wszystkie powłoki interpretują jako wywołaniecmd
z argumentami (niezależnie od ich zawartości).Ograniczenia:
yash
jest to powłoka zdalnego logowania, nie można przekazać polecenia, którego argumenty zawierają nieprawidłowe znaki, ale jest to ograniczenieyash
polegające na tym, że i tak nie można się obejść.sh
tym zakłada również, że system zdalny maprintf
polecenie.Aby zrozumieć, jak to działa, musisz wiedzieć, jak działa cytowanie w różnych powłokach:
'...'
są mocnymi cytatami bez charakteru specjalnego."..."
są słabymi cytatami, w których"
można uniknąć znaku ukośnika odwrotnego.csh
. Tak samo jak Bourne, tyle że"
nie można uciec do środka"..."
. Również znak nowej linii musi być poprzedzony odwrotnym ukośnikiem. I!
powoduje problemy nawet w pojedynczych cudzysłowach.rc
. Jedyne cytaty to'...'
(mocne). Pojedynczy cytat w ramach pojedynczych cytatów jest wprowadzany jako''
(jak'...''...'
). Podwójne cudzysłowy lub odwrotne ukośniki nie są wyjątkowe.es
. Podobnie jak rc, z wyjątkiem cudzysłowów, odwrotny ukośnik może uciec od pojedynczego cudzysłowu.fish
: tak samo jak Bourne, tyle że ukośnik ucieka do'
środka'...'
.Przy wszystkich tych przeciwnościach łatwo zauważyć, że nie można w sposób wiarygodny zacytować argumentów wiersza poleceń, aby działał ze wszystkimi powłokami.
Używanie pojedynczych cudzysłowów jak w:
działa we wszystkich oprócz:
nie działa
rc
.nie działa
csh
.nie działa
fish
.Jednak powinniśmy być w stanie obejść większość z tych problemów, jeśli uda nam się zachować tych problematycznych znaków w zmiennych, jak ukośnik w
$b
, w pojedynczy cudzysłów$q
, znak nowej linii w$n
(i!
w$x
dla csh historii ekspansji) w powłoce niezależny sposób.działałby we wszystkich powłokach. Mimo to nadal nie działałoby to w przypadku nowej linii
csh
. Jeśli$n
zawiera znak nowej liniicsh
, musisz napisać,$n:q
aby rozwinął się do nowej linii i nie będzie to działać w przypadku innych powłok. Więc to, co w końcu robimy, to dzwonieniesh
ish
rozwijanie ich$n
. Oznacza to również konieczność wykonania dwóch poziomów cytowania, jednego dla zdalnej powłoki logowania i jednego dlash
.W
$preamble
tym kodzie jest najtrudniejsza część. To sprawia, że korzystanie z różnymi cytując zasad we wszystkich muszli mieć pewne fragmenty kodu interpretowane tylko przez jedno z muszli (podczas gdy jest to w komentarzu dla innych), z których każdy tylko definiującej te$b
,$q
,$n
,$x
zmienne dla ich odpowiedniego zbiornika.Oto kod powłoki, który zostałby zinterpretowany przez powłokę logowania użytkownika zdalnego na
host
twoim przykładzie:Ten kod uruchamia to samo polecenie, gdy jest interpretowane przez jedną z obsługiwanych powłok.
źródło
tl; dr
Aby uzyskać bardziej skomplikowane rozwiązanie, przeczytaj komentarze i sprawdź drugą odpowiedź .
opis
Moje rozwiązanie nie będzie działać z
bash
powłokami innymi niż powłoki. Ale zakładając, że jestbash
z drugiej strony, sprawy stają się prostsze. Moim pomysłem jest ponowne wykorzystanieprintf "%q"
do ucieczki. Ogólnie rzecz biorąc, bardziej czytelny jest skrypt na drugim końcu, który akceptuje argumenty. Ale jeśli polecenie jest krótkie, prawdopodobnie dobrze jest je wstawić. Oto kilka przykładowych funkcji używanych w skryptach:local.sh
:remote.sh
:Wyjście:
Alternatywnie możesz sam wykonać
printf
pracę, jeśli wiesz, co robisz:źródło
bash
będzie dostępna na zdalnym komputerze. Istnieje również kilka problemów z brakującymi cytatami, które mogłyby powodować problemy z białymi znakami i znakami wieloznacznymi.bash
powłok. Ale mam nadzieję, że ludzie znajdą to w użyciu. Próbowałem jednak rozwiązać inne problemy. Nie krępuj się powiedzieć mi, czy coś mi brakuje oprócz tegobash
.ssh_run user@host "${cmd[@]}"
). Wciąż brakuje niektórych cytatów.printf %q
nie jest bezpieczne do użycia w różnych lokalizacjach (i jest również dość błędne; na przykład w lokalizacjach używających zestawu znaków BIG5, to (4.3.48) cytujeε
jakoα`
!). W tym celu najlepiej zacytować wszystko, używając pojedynczych cytatów, tak jakshquote()
w mojej odpowiedzi.