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:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
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 do env
. 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życiu sh
składni cytowania.
shquote() {
LC_ALL=C awk -v q=\' '
BEGIN{
for (i=1; i<ARGC; i++) {
gsub(q, q "\\" q q, ARGV[i])
printf "%s ", q ARGV[i] q
}
print ""
}' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh
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ę od LC_
.
W takich przypadkach możesz mieć LC_CODE
na przykład zmienną zawierającą kod dzielony sh
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):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
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:
#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};
@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
push @ssh, $arg;
}
if (@ARGV) {
for (@ARGV) {
s/'/'\$q\$b\$q\$q'/g;
s/\n/'\$q'\$n'\$q'/g;
s/!/'\$x'/g;
s/\\/'\$b'/g;
$_ = "\$q'$_'\$q";
}
push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}
exec @ssh;
To jest perl
skrypt opakowania ssh
. Nazywam to sexec
. Nazywasz to tak:
sexec [ssh-options] user@host -- cmd and its args
więc w twoim przykładzie:
sexec user@host -- "${cmd[@]}"
Opakowanie zamienia cmd and its args
się w wiersz poleceń, który wszystkie powłoki interpretują jako wywołanie cmd
z argumentami (niezależnie od ich zawartości).
Ograniczenia:
- Preambuła i sposób cytowania polecenia oznaczają, że zdalna linia poleceń jest znacznie większa, co oznacza, że limit maksymalnego rozmiaru wiersza poleceń zostanie osiągnięty wcześniej.
- Testowałem to tylko z: powłoką Bourne'a (z heirloom Toolchest), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish, jak znaleziono w ostatnim systemie Debian i / bin / sh, / usr / bin / ksh, / bin / csh i / usr / xpg4 / bin / sh w systemie Solaris 10.
- Jeśli
yash
jest to powłoka zdalnego logowania, nie można przekazać polecenia, którego argumenty zawierają nieprawidłowe znaki, ale jest to ograniczenie yash
polegające na tym, że i tak nie można się obejść.
- Niektóre powłoki, takie jak csh lub bash, odczytują niektóre pliki startowe, gdy są wywoływane przez ssh. Zakładamy, że nie zmieniają one radykalnie zachowania, więc preambuła nadal działa.
- poza
sh
tym zakłada również, że system zdalny ma printf
polecenie.
Aby zrozumieć, jak to działa, musisz wiedzieć, jak działa cytowanie w różnych powłokach:
- Bourne:
'...'
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:
'foo' 'bar'
działa we wszystkich oprócz:
'echo' 'It'\''s'
nie działa rc
.
'echo' 'foo
bar'
nie działa csh
.
'echo' 'foo\'
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.
'echo' 'It'$q's'
'echo' 'foo'$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 linii csh
, 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 dzwonienie sh
i sh
rozwijanie ich $n
. Oznacza to również konieczność wykonania dwóch poziomów cytowania, jednego dla zdalnej powłoki logowania i jednego dla sh
.
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:
printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q
Ten kod uruchamia to samo polecenie, gdy jest interpretowane przez jedną z obsługiwanych powłok.
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?