Ich glaube nicht, dass eine Implementierung von ssh
eine native Möglichkeit hat, einen Befehl vom Client zum Server zu übertragen, ohne eine Shell zu verwenden.
Jetzt kann es einfacher werden, wenn Sie der Remote-Shell anweisen, nur einen bestimmten Interpreter ( sh
für den wir die erwartete Syntax kennen) auszuführen und den Code auf andere Weise auszuführen.
Das andere Mittel kann beispielsweise eine Standardeingabe oder eine Umgebungsvariable sein .
Wenn beides nicht verwendet werden kann, schlage ich eine hackige dritte Lösung vor.
Stdin verwenden
Wenn Sie dem Remote-Befehl keine Daten zuführen müssen, ist dies die einfachste Lösung.
Wenn Sie wissen, dass der Remote-Host einen xargs
Befehl hat, der die -0
Option unterstützt, und der Befehl nicht zu groß ist, können Sie Folgendes tun:
printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'
Diese xargs -0 env --
Befehlszeile wird für alle diese Shell-Familien gleich interpretiert. xargs
Liest die durch Nullen getrennte Liste der Argumente für stdin und übergibt diese als Argumente an env
. Dies setzt voraus, dass das erste Argument (der Befehlsname) keine =
Zeichen enthält .
Oder Sie können es sh
auf dem Remote-Host verwenden, nachdem Sie jedes Element in Anführungszeichen gesetzt haben 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
Umgebungsvariablen verwenden
Wenn Sie nun einige Daten vom Client an die stdin des Remote-Befehls senden müssen, funktioniert die oben beschriebene Lösung nicht.
Bei einigen ssh
Serverbereitstellungen können jedoch beliebige Umgebungsvariablen vom Client an den Server übergeben werden. Beispielsweise erlauben viele OpenSh-Implementierungen auf Debian-basierten Systemen die Übergabe von Variablen, deren Name mit beginnt LC_
.
In diesen Fällen könnten Sie eine LC_CODE
Variable haben, die beispielsweise den oben angegebenen sh
Code enthält und sh -c 'eval "$LC_CODE"'
auf dem Remote-Host ausgeführt wird, nachdem Sie Ihrem Client mitgeteilt haben, diese Variable zu übergeben (auch dies ist eine Befehlszeile, die in jeder Shell gleich interpretiert wird):
LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
sh -c '\''eval "$LC_CODE"'\'
Erstellen einer Befehlszeile, die mit allen Shell-Familien kompatibel ist
Wenn keine der oben genannten Optionen akzeptabel ist (weil Sie stdin benötigen und sshd keine Variablen akzeptiert oder weil Sie eine generische Lösung benötigen), müssen Sie eine Befehlszeile für den Remote-Host vorbereiten, die mit allen kompatibel ist unterstützte Muscheln.
Dies ist besonders schwierig, da alle diese Shells (Bourne, csh, rc, es, fish) eine eigene unterschiedliche Syntax und insbesondere unterschiedliche Zitiermechanismen haben und einige von ihnen Einschränkungen aufweisen, die nur schwer zu umgehen sind.
Hier ist eine Lösung, die ich mir ausgedacht habe und die ich weiter unten beschreibe:
#! /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;
Das ist ein perl
Wrapper-Skript ssh
. Ich nenne es sexec
. Du nennst es so:
sexec [ssh-options] user@host -- cmd and its args
Also in deinem Beispiel:
sexec user@host -- "${cmd[@]}"
Und der Wrapper wird cmd and its args
zu einer Befehlszeile, die alle Shells als Aufruf cmd
mit ihren Argumenten interpretieren (unabhängig von ihrem Inhalt).
Einschränkungen:
- Die Präambel und die Art und Weise, wie der Befehl in Anführungszeichen gesetzt wird, bedeuten, dass die Remote-Befehlszeile erheblich größer wird, was bedeutet, dass die Grenze für die maximale Größe einer Befehlszeile eher erreicht wird.
- Ich habe es nur getestet mit: Bourne-Shell (aus der Erbstück-Werkzeugkiste), Dash, Bash, Zsh, Mksh, Lksh, Yash, Ksh93, RC, ES, Akanga, CSH, TCSH, Fisch, wie auf einem aktuellen Debian-System und / bin / sh, / usr / bin / ksh, / bin / csh und / usr / xpg4 / bin / sh unter Solaris 10.
- Wenn
yash
es sich um die Remote-Anmeldeshell handelt, können Sie keinen Befehl übergeben, dessen Argumente ungültige Zeichen enthalten. Dies ist jedoch eine Einschränkung yash
, die Sie ohnehin nicht umgehen können.
- Einige Shells wie csh oder bash lesen einige Startdateien, wenn sie über ssh aufgerufen werden. Wir gehen davon aus, dass dies das Verhalten nicht dramatisch verändert, so dass die Präambel weiterhin funktioniert.
sh
Außerdem wird davon ausgegangen , dass das ferne System über den printf
Befehl verfügt.
Um zu verstehen, wie es funktioniert, müssen Sie wissen, wie das Zitieren in den verschiedenen Shells funktioniert:
- Bourne:
'...'
sind starke Anführungszeichen ohne besonderen Charakter. "..."
sind schwache Anführungszeichen, denen "
mit Backslash begegnet werden kann.
csh
. Wie Bourne, nur dass "
es nicht drinnen entkommen kann "..."
. Außerdem muss ein Zeilenumbruch mit einem Backslash vorangestellt werden. Und !
verursacht Probleme auch in einfachen Anführungszeichen.
rc
. Die einzigen Anführungszeichen sind '...'
(stark). Ein einfaches Anführungszeichen in einfachen Anführungszeichen wird als ''
(like '...''...'
) eingegeben . Doppelte Anführungszeichen oder Backslashes sind keine Besonderheiten.
es
. Entspricht rc, mit der Ausnahme, dass Backslash außerhalb von Anführungszeichen vor einem einzelnen Anführungszeichen stehen kann.
fish
: wie Bourne mit dem Unterschied, dass ein Backslash im '
Inneren auftritt '...'
.
Bei all diesen Einschränkungen ist leicht zu erkennen, dass man Befehlszeilenargumente nicht zuverlässig zitieren kann, so dass es mit allen Shells funktioniert.
Verwenden von einfachen Anführungszeichen wie in:
'foo' 'bar'
funktioniert in allen aber:
'echo' 'It'\''s'
würde nicht funktionieren rc
.
'echo' 'foo
bar'
würde nicht funktionieren csh
.
'echo' 'foo\'
würde nicht funktionieren fish
.
Allerdings sollten wir in der Lage sein , um die meisten dieser Probleme zu arbeiten , wenn wir die problematischen Zeichen in Variablen, wie Backslash in speichern verwalten $b
, Apostroph in $q
, Newline in $n
(und !
in $x
für csh Geschichte Expansion) in einer Schale unabhängige Art und Weise.
'echo' 'It'$q's'
'echo' 'foo'$b
würde in allen Schalen funktionieren. Das würde aber für newline immer noch nicht funktionieren csh
. Wenn $n
newline enthält, csh
müssen Sie es so schreiben, $n:q
dass es zu einer newline erweitert wird, und das funktioniert nicht für andere Shells. Also rufen wir stattdessen hier an sh
und haben diese sh
erweitert $n
. Dies bedeutet auch, dass zwei Angebotsstufen erforderlich sind, eine für die Remote-Anmeldeshell und eine für sh
.
Der $preamble
in diesem Code ist der schwierigste Teil. Es nutzt die verschiedenen zitiert Regeln in allen Schalen einige Abschnitte des Codes durch nur eine der Schalen interpretiert zu haben (während es für die anderen Kommentar gesetzt ist) , von denen jeder nur diejenigen definieren $b
, $q
, $n
, $x
Variablen für ihre jeweiligen Shell.
Hier ist der Shell-Code, der von der Login-Shell des entfernten Benutzers host
für Ihr Beispiel interpretiert wird :
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
Dieser Code führt am Ende denselben Befehl aus, wenn er von einer der unterstützten Shells interpretiert wird.
cmd
Argument/bin/sh -c
wäre, wir würden in 99% aller Fälle mit einer Posix-Shell enden, nicht wahr? Natürlich ist es auf diese Weise etwas schmerzhafter, Sonderzeichen zu entkommen, aber würde dies das ursprüngliche Problem lösen?