Não acho que nenhuma implementação ssh
tenha uma maneira nativa de passar um comando do cliente para o servidor sem envolver um shell.
Agora, as coisas podem ficar mais fáceis se você puder dizer ao shell remoto para executar apenas um intérprete específico (como sh
, para o qual conhecemos a sintaxe esperada) e fornecer o código para executar por outro meio.
Essa outra média pode ser, por exemplo , entrada padrão ou uma variável de ambiente .
Quando nenhum deles pode ser usado, proponho uma terceira solução hacky abaixo.
Usando stdin
Se você não precisar alimentar nenhum dado com o comando remoto, essa é a solução mais fácil.
Se você sabe que o host remoto possui um xargs
comando que suporta a -0
opção e o comando não é muito grande, você pode:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Essa xargs -0 env --
linha de comando é interpretada da mesma forma com todas essas famílias de shell. xargs
lê a lista de argumentos delimitados por nulo no stdin e os passa como argumentos para env
. Isso pressupõe que o primeiro argumento (o nome do comando) não contenha =
caracteres.
Ou você pode usar sh
no host remoto depois de citar cada elemento usando a sh
sintaxe de citação.
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
Usando variáveis de ambiente
Agora, se você precisar alimentar alguns dados do cliente para o stdin do comando remoto, a solução acima não funcionará.
No ssh
entanto, algumas implantações de servidor permitem a passagem de variáveis de ambiente arbitrárias do cliente para o servidor. Por exemplo, muitas implantações openssh em sistemas baseados no Debian permitem passar variáveis cujo nome começa com LC_
.
Nesses casos, você pode ter uma LC_CODE
variável, por exemplo, contendo o código shquoted sh
, como descrito acima, e executar sh -c 'eval "$LC_CODE"'
no host remoto depois de solicitar ao seu cliente que passe essa variável (novamente, essa é uma linha de comando que é interpretada da mesma forma em todos os shell):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Construindo uma linha de comando compatível com todas as famílias de shell
Se nenhuma das opções acima for aceitável (porque você precisa de stdin e sshd não aceita nenhuma variável ou porque precisa de uma solução genérica), precisará preparar uma linha de comando para o host remoto compatível com todos os conchas suportadas.
Isso é particularmente complicado porque todas essas conchas (Bourne, csh, rc, es, fish) têm sua própria sintaxe diferente e, em particular, diferentes mecanismos de citação e algumas delas têm limitações difíceis de contornar.
Aqui está uma solução que eu criei, eu a descrevo mais abaixo:
#! /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;
Esse é um perl
script de wrapper ssh
. Eu chamo isso sexec
. Você chama assim:
sexec [ssh-options] user@host -- cmd and its args
então no seu exemplo:
sexec user@host -- "${cmd[@]}"
E o wrapper se transforma cmd and its args
em uma linha de comando que todos os shells acabam interpretando como chamando cmd
com seus argumentos (independentemente do conteúdo).
Limitações:
- O preâmbulo e a maneira como o comando é citado significa que a linha de comando remota acaba sendo significativamente maior, o que significa que o limite do tamanho máximo de uma linha de comando será atingido mais rapidamente.
- Eu só testei com: shell Bourne (do heirloom toolchest), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish, encontrado em um sistema Debian recente e / bin / sh, / usr / bin / ksh, / bin / csh e / usr / xpg4 / bin / sh no Solaris 10.
- Se
yash
for o shell de logon remoto, você não pode passar um comando cujos argumentos contenham caracteres inválidos, mas isso é uma limitação, yash
pois você não pode contornar isso de qualquer maneira.
- Alguns shells como csh ou bash leem alguns arquivos de inicialização quando invocados pelo ssh. Assumimos que eles não mudam drasticamente o comportamento para que o preâmbulo ainda funcione.
- ao lado
sh
, também assume que o sistema remoto possui o printf
comando.
Para entender como ele funciona, você precisa saber como a citação funciona nos diferentes shells:
- Bourne:
'...'
são citações fortes, sem caráter especial. "..."
são aspas fracas onde "
podem ser escapadas com barra invertida.
csh
. O mesmo que Bourne, exceto que "
não pode ser escapado por dentro "..."
. Além disso, um caractere de nova linha deve ser inserido como prefixo com uma barra invertida. E !
causa problemas mesmo dentro de aspas simples.
rc
. As únicas citações são '...'
(forte). Uma citação única entre aspas simples é inserida como ''
(como '...''...'
). Aspas duplas ou barras invertidas não são especiais.
es
. O mesmo que rc, exceto que aspas externas, a barra invertida pode escapar de uma única aspas.
fish
: o mesmo que Bourne, exceto que a barra invertida escapa por '
dentro '...'
.
Com todas essas restrições, é fácil ver que não é possível citar argumentos de linha de comando de maneira confiável, para que funcione com todos os shells.
Usando aspas simples como em:
'foo' 'bar'
funciona em todos, exceto:
'echo' 'It'\''s'
não funcionaria rc
.
'echo' 'foo
bar'
não funcionaria csh
.
'echo' 'foo\'
não funcionaria fish
.
No entanto, deve ser capaz de resolver a maioria desses problemas se conseguirmos armazenar esses personagens problemáticos em variáveis, como barra invertida no $b
, aspas simples em $q
, nova linha na $n
(e !
no $x
de expansão história CSH) de forma independente shell.
'echo' 'It'$q's'
'echo' 'foo'$b
funcionaria em todas as conchas. No entanto, isso ainda não funcionaria para a nova linha csh
. Se $n
contiver nova linha, em csh
, você deverá escrevê- $n:q
la para expandir para uma nova linha e isso não funcionará para outros shells. Então, o que acabamos fazendo aqui é chamar sh
e sh
expandi-los $n
. Isso também significa ter que fazer dois níveis de citação, um para o shell de login remoto e outro para sh
.
O $preamble
código é a parte mais complicada. Ele faz uso das várias regras citando diferentes em todas as conchas de ter algumas seções do código interpretado por apenas uma das conchas (enquanto ele está comentado para os outros) cada um dos quais apenas definindo os $b
, $q
, $n
, $x
variáveis para seus respectivos shell.
Aqui está o código do shell que seria interpretado pelo shell de login do usuário remoto no host
seu exemplo:
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
Esse código acaba executando o mesmo comando quando interpretado por qualquer um dos shells suportados.
cmd
argumento fosse/bin/sh -c
que acabaríamos com um shell posix em 99% de todos os casos, não é? É claro que escapar de caracteres especiais é um pouco mais doloroso, mas isso resolveria o problema inicial?