Je ne pense pas qu'une implémentation de ssh
ait une manière native de passer une commande du client au serveur sans impliquer un shell.
Maintenant, les choses peuvent devenir plus faciles si vous pouvez dire au shell distant d'exécuter uniquement un interpréteur spécifique (comme sh
, pour lequel nous connaissons la syntaxe attendue) et de donner le code à exécuter par un autre moyen.
Cet autre moyen peut être par exemple une entrée standard ou une variable d'environnement .
Lorsque ni l'un ni l'autre ne peut être utilisé, je propose une troisième solution hacky ci-dessous.
Utilisation de stdin
Si vous n'avez pas besoin de fournir de données à la commande à distance, c'est la solution la plus simple.
Si vous savez que l'hôte distant possède une xargs
commande qui prend en charge l' -0
option et que la commande n'est pas trop grande, vous pouvez faire:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Cette xargs -0 env --
ligne de commande est interprétée de la même manière avec toutes ces familles de shell. xargs
lit la liste d'arguments séparés par des valeurs nulles sur stdin et les transmet comme arguments à env
. Cela suppose que le premier argument (le nom de la commande) ne contient pas de =
caractères.
Ou vous pouvez utiliser sh
sur l'hôte distant après avoir cité chaque élément en utilisant la sh
syntaxe de citation.
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
Utilisation de variables d'environnement
Maintenant, si vous avez besoin de fournir des données du client au stdin de la commande à distance, la solution ci-dessus ne fonctionnera pas.
Certains ssh
déploiements de serveur permettent cependant de transmettre des variables d'environnement arbitraires du client au serveur. Par exemple, de nombreux déploiements openssh sur des systèmes basés sur Debian permettent de passer des variables dont le nom commence par LC_
.
Dans ces cas, vous pourriez avoir une LC_CODE
variable contenant par exemple le code shquoted sh
comme ci-dessus et s'exécuter sh -c 'eval "$LC_CODE"'
sur l'hôte distant après avoir dit à votre client de passer cette variable (encore une fois, c'est une ligne de commande qui est interprétée de la même manière dans chaque shell):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Construire une ligne de commande compatible avec toutes les familles de shell
Si aucune des options ci-dessus n'est acceptable (parce que vous avez besoin de stdin et que sshd n'accepte aucune variable, ou parce que vous avez besoin d'une solution générique), alors vous devrez préparer une ligne de commande pour l'hôte distant qui soit compatible avec tous coquilles supportées.
C'est particulièrement délicat car tous ces shells (Bourne, csh, rc, es, fish) ont leur propre syntaxe différente, et en particulier différents mécanismes de citation et certains d'entre eux ont des limitations difficiles à contourner.
Voici une solution que j'ai trouvée, je la décris plus bas:
#! /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;
C'est un perl
script wrapper autour ssh
. Je l'appelle sexec
. Vous l'appelez comme:
sexec [ssh-options] user@host -- cmd and its args
donc dans votre exemple:
sexec user@host -- "${cmd[@]}"
Et l'encapsuleur se transforme cmd and its args
en ligne de commande que tous les shells finissent par interpréter comme appelant cmd
avec ses arguments (indépendamment de leur contenu).
Limites:
- Le préambule et la façon dont la commande est citée signifie que la ligne de commande distante finit par être beaucoup plus grande, ce qui signifie que la limite de la taille maximale d'une ligne de commande sera atteinte plus tôt.
- Je ne l'ai testé qu'avec: Bourne shell (de heirloom toolchest), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish comme sur un système Debian récent et / bin / sh, / usr / bin / ksh, / bin / csh et / usr / xpg4 / bin / sh sur Solaris 10.
- Si
yash
est le shell de connexion à distance, vous ne pouvez pas passer une commande dont les arguments contiennent des caractères non valides, mais c'est une limitation dans la mesure yash
où vous ne pouvez pas contourner de toute façon.
- Certains shells comme csh ou bash lisent certains fichiers de démarrage lorsqu'ils sont invoqués via ssh. Nous supposons que ceux-ci ne modifient pas le comportement de façon spectaculaire afin que le préambule fonctionne toujours.
- à côté
sh
, il suppose également que le système distant a la printf
commande.
Pour comprendre comment cela fonctionne, vous devez savoir comment fonctionne la citation dans les différents shells:
- Bourne:
'...'
sont des citations fortes sans caractère spécial. "..."
sont des guillemets faibles où il "
est possible d'échapper avec une barre oblique inverse.
csh
. Identique à Bourne, sauf qu'il "
ne peut pas s'échapper à l'intérieur "..."
. De plus, un caractère de nouvelle ligne doit être saisi, préfixé par une barre oblique inverse. Et !
cause des problèmes même à l'intérieur des guillemets simples.
rc
. Les seules citations sont '...'
(fortes). Un guillemet simple entre guillemets simples est entré comme ''
(comme '...''...'
). Les guillemets doubles ou les contre-obliques ne sont pas spéciaux.
es
. Identique à rc, sauf que les guillemets extérieurs, la barre oblique inverse peut échapper à un guillemet simple.
fish
: identique à Bourne sauf que la barre oblique inverse s'échappe à l' '
intérieur '...'
.
Avec toutes ces contraintes, il est facile de voir que l'on ne peut pas citer de manière fiable des arguments de ligne de commande pour qu'il fonctionne avec tous les shells.
Utiliser des guillemets simples comme dans:
'foo' 'bar'
fonctionne dans tous, mais:
'echo' 'It'\''s'
ne fonctionnerait pas rc
.
'echo' 'foo
bar'
ne fonctionnerait pas csh
.
'echo' 'foo\'
ne fonctionnerait pas fish
.
Cependant, nous devrions être en mesure de contourner la plupart de ces problèmes si nous parvenons à stocker ces caractères problématiques dans des variables, comme la barre oblique inverse $b
, les guillemets simples $q
, la nouvelle ligne dans $n
(et !
dans $x
pour l'expansion de l'historique csh) de manière indépendante du shell.
'echo' 'It'$q's'
'echo' 'foo'$b
fonctionnerait dans tous les obus. Cela ne fonctionnerait toujours pas pour newline csh
. Si $n
contient un retour à la ligne, dans csh
, vous devez l'écrire $n:q
pour qu'il s'étende à un retour à la ligne et cela ne fonctionnera pas pour les autres shells. Donc, ce que nous finissons par faire ici, c'est d'appeler sh
et de les sh
étendre $n
. Cela signifie également avoir à faire deux niveaux de devis, un pour le shell de connexion à distance et un pour sh
.
Le $preamble
dans ce code est la partie la plus délicate. Il utilise les différentes différentes règles de cotation dans toutes les coquilles d'avoir certaines sections du code interprété par une seule des obus (alors qu'il est commentée pour les autres) , dont chacun vient définir les $b
, $q
, $n
, $x
variables pour leur coquille respective.
Voici le code shell qui serait interprété par le shell de connexion de l'utilisateur distant sur host
pour votre exemple:
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
Ce code finit par exécuter la même commande lorsqu'il est interprété par l'un des shells pris en charge.
cmd
argument était que/bin/sh -c
nous nous retrouverions avec un shell posix dans 99% des cas, n'est-ce pas? Bien sûr, échapper des caractères spéciaux est un peu plus douloureux de cette façon, mais cela résoudrait-il le problème initial?