Я не думаю, что в какой-либо реализации 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% случаев, не так ли? Конечно, экранировать специальные символы немного сложнее, но решит ли это начальную проблему?