Não acho que nenhuma implementação sshtenha 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 xargscomando que suporta a -0opçã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. xargslê 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 shno host remoto depois de citar cada elemento usando a shsintaxe 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 sshentanto, 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_CODEvariá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 perlscript 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 argsem uma linha de comando que todos os shells acabam interpretando como chamando cmdcom 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
yashfor o shell de logon remoto, você não pode passar um comando cujos argumentos contenham caracteres inválidos, mas isso é uma limitação, yashpois 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 printfcomando.
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 $xde 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 $ncontiver nova linha, em csh, você deverá escrevê- $n:qla para expandir para uma nova linha e isso não funcionará para outros shells. Então, o que acabamos fazendo aqui é chamar she shexpandi-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 $preamblecó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, $xvariáveis para seus respectivos shell.
Aqui está o código do shell que seria interpretado pelo shell de login do usuário remoto no hostseu 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.
cmdargumento fosse/bin/sh -cque 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?