Я не думаю, что в какой-либо реализации sshесть собственный способ передачи команды от клиента к серверу без использования оболочки.
Теперь все может стать проще, если вы можете указать удаленной оболочке запускать только определенный интерпретатор (например sh, для которого мы знаем ожидаемый синтаксис) и дать код для выполнения другим способом.
Этим другим средством может быть, например, стандартный ввод или переменная окружения .
Когда ни один из них не может быть использован, я предлагаю хакерское третье решение ниже.
Использование stdin
Если вам не нужно передавать какие-либо данные в удаленную команду, это самое простое решение.
Если вы знаете, что на удаленном хосте есть xargsкоманда, которая поддерживает эту -0опцию, и эта команда не слишком велика, вы можете сделать:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Эта xargs -0 env --командная строка интерпретируется одинаково со всеми этими семействами оболочек. xargsчитает список аргументов с нулевым разделителем в stdin и передает их в качестве аргументов env. Это предполагает, что первый аргумент (имя команды) не содержит =символов.
Или вы можете использовать shна удаленном хосте после цитирования каждого элемента с использованием shсинтаксиса цитирования.
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
Использование переменных среды
Теперь, если вам нужно передать некоторые данные от клиента на стандартный ввод удаленной команды, вышеуказанное решение не будет работать.
Однако некоторые sshразвертывания сервера позволяют передавать произвольные переменные среды от клиента к серверу. Например, многие развертывания openssh в системах на основе Debian позволяют передавать переменные, имя которых начинается с LC_.
В этих случаях вы можете иметь LC_CODEпеременную, например, содержащую код в кавычках, sh как указано выше, и запускать sh -c 'eval "$LC_CODE"'на удаленном хосте после того, как попросите своего клиента передать эту переменную (опять же, это командная строка, которая интерпретируется одинаково в каждой оболочке):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Создание командной строки, совместимой со всеми семействами оболочек
Если ни один из приведенных выше вариантов не является приемлемым (поскольку вам нужен stdin, а sshd не принимает никаких переменных, или потому, что вам нужно универсальное решение), вам придется подготовить командную строку для удаленного хоста, совместимого со всеми поддерживаемые снаряды.
Это особенно сложно, потому что все эти оболочки (Bourne, csh, rc, es, fish) имеют свой собственный синтаксис и, в частности, разные механизмы цитирования, а некоторые из них имеют ограничения, которые трудно обойти.
Вот решение, которое я придумала, я опишу его ниже:
#! /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;
Это perlсценарий обертки вокруг ssh. Я называю это sexec. Вы называете это как:
sexec [ssh-options] user@host -- cmd and its args
Итак, в вашем примере:
sexec user@host -- "${cmd[@]}"
И оболочка превращается cmd and its argsв командную строку, которую все оболочки интерпретируют как вызовы cmdс ее аргументами (независимо от их содержания).
Ограничения:
- Преамбула и способ указания команды означают, что удаленная командная строка оказывается значительно больше, что означает, что ограничение на максимальный размер командной строки будет достигнуто раньше.
- Я протестировал его только с: оболочкой Bourne (из набора инструментов семейной реликвии), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish, как было установлено в недавней системе Debian, и / bin / sh, / usr / bin / ksh, / bin / csh и / usr / xpg4 / bin / sh на Solaris 10.
- Если
yashэто оболочка удаленного входа, вы не можете передать команду, аргументы которой содержат недопустимые символы, но это ограничение в yashтом, что вы все равно не можете обойтись.
- Некоторые оболочки, такие как csh или bash, читают некоторые файлы запуска при вызове через ssh. Мы предполагаем, что они не изменяют поведение резко, так что преамбула все еще работает.
- кроме того
sh, он также предполагает, что удаленная система имеет printfкоманду.
Чтобы понять, как это работает, вам нужно знать, как работает цитирование в различных оболочках:
- Борн:
'...'это сильные цитаты без специальных символов. "..."являются слабыми кавычками, где "можно избежать обратной косой черты.
csh, То же самое, что и Борн, за исключением того, что "нельзя сбежать внутрь "...". Также символ новой строки должен вводиться с префиксом с обратной косой чертой. И !вызывает проблемы даже внутри одинарных кавычек.
rc, Единственные цитаты '...'(сильные). Одиночная кавычка в одинарных кавычках вводится как ''(как '...''...'). Двойные кавычки или обратная косая черта не являются особенными.
es, То же, что и для rc, за исключением того, что вне кавычек обратная косая черта может выходить за пределы одной кавычки.
fish: так же, как Борн, за исключением того, что обратный слеш убегает 'внутрь '...'.
Со всеми этими ограничениями легко заметить, что нельзя надежно процитировать аргументы командной строки, чтобы она работала со всеми оболочками.
Использование одинарных кавычек, как в:
'foo' 'bar'
работает во всех, кроме:
'echo' 'It'\''s'
не будет работать в rc.
'echo' 'foo
bar'
не будет работать в csh.
'echo' 'foo\'
не будет работать в fish.
Однако мы сможем обойти большинство из этих проблем, если нам удастся сохранить эти проблемные символы в переменных, таких как обратная косая черта $b, одинарная кавычка $q, новая строка в $n(и !в $xдля расширения истории csh) независимым способом оболочки.
'echo' 'It'$q's'
'echo' 'foo'$b
будет работать во всех снарядах. Это все равно не будет работать для новой строки, cshхотя. Если в нем $nсодержится символ новой строки, cshвы должны написать его так, $n:qчтобы он расширился до новой строки, и это не будет работать для других оболочек. Итак, то, что мы в итоге делаем вместо этого, - это звонить shи shрасширять их $n. Это также означает необходимость выполнения двух уровней цитирования: один для оболочки удаленного входа и один для sh.
В $preambleэтом коде самая сложная часть. Это делает использование различных различного котирования правил во всех оболочках , чтобы иметь некоторые участки кода истолкован лишь одной из оболочек ( в то время как закомментированы для других) , каждого из которых только определяющих те $b, $q, $n, $xпеременных для их соответствующей оболочки.
Вот код оболочки, который будет интерпретирован оболочкой входа удаленного пользователя hostдля вашего примера:
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
Этот код в конечном итоге выполняет ту же команду при интерпретации любым из поддерживаемых оболочек.
cmdаргумент был таким,/bin/sh -cмы бы в конечном итоге создали оболочку posix в 99% случаев, не так ли? Конечно, экранировать специальные символы немного сложнее, но решит ли это начальную проблему?