ssh
hat eine nervige Funktion, wenn Sie ausführen:
ssh user@host cmd and "here's" "one arg"
Anstatt das cmd
mit seinen Argumenten host
auszuführen, verkettet es das cmd
und die Argumente mit Leerzeichen und führt eine Shell aus host
, um den resultierenden String zu interpretieren (ich denke, deshalb heißt sie ssh
und nicht sexec
).
Schlimmer noch, Sie wissen nicht, mit welcher Shell diese Zeichenfolge interpretiert werden soll, da es sich bei der Login-Shell user
nicht einmal um eine Bourne- tcsh
Shell fish
handelt, da immer noch Benutzer als Login-Shell fungieren und die Zahl der Nutzer steigt.
Gibt es einen Weg, das zu umgehen?
Angenommen , ich habe einen Befehl als eine Liste von Argumenten in einer gespeicherten bash
Matrix, von denen jeder eine beliebige Folge von Nicht-Null - Bytes enthalten kann, ist es eine Möglichkeit , es zu haben Ausführung auf host
wie user
in konsistenter Weise und unabhängig von der Login - Shell , dass user
auf host
(Was wir annehmen werden, ist eine der wichtigsten Unix-Shell-Familien: Bourne, csh, rc / es, fish)?
Eine andere vernünftige Annahme, die ich machen kann, ist, dass ein sh
Befehl host
verfügbar $PATH
ist, der Bourne-kompatibel ist.
Beispiel:
cmd=(
'printf'
'<%s>\n'
'arg with $and spaces'
'' # empty
$'even\n* * *\nnewlines'
"and 'single quotes'"
'!!'
)
Ich kann es lokal ausführen mit ksh
/ zsh
/ bash
/ yash
als:
$ "${cmd[@]}"
<arg with $and spaces>
<>
<even
* * *
newlines>
<and 'single quotes'>
<!!>
oder
env "${cmd[@]}"
oder
xterm -hold -e "${cmd[@]}"
...
Wie würde ich es host
als user
über laufen ssh
?
ssh user@host "${cmd[@]}"
offensichtlich wird nicht funktionieren.
ssh user@host "$(printf ' %q' exec "${cmd[@]}")"
Dies würde nur funktionieren, wenn die Anmeldeshell des Remotebenutzers mit der lokalen Shell identisch wäre (oder das Zitieren so versteht, wie es printf %q
in der lokalen Shell erzeugt wird) und im selben Gebietsschema ausgeführt wird.
quelle
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?ssh host sh -c 'some cmd'
wie ausgeführtssh host 'sh -c some cmd'
werden, wird diesesh -c some cmd
Befehlszeile von der Anmeldeshell des Remotebenutzers interpretiert . Wir müssen den Befehl in der korrekten Syntax für diese Shell schreiben (und wir wissen nicht, um welche es sich handelt),sh
damit er dort mit-c
undsome cmd
Argumenten aufgerufen wird .sh -c 'some cmd'
undsome cmd
Kommandozeilen werden in all diesen Shells zufällig gleich interpretiert. Was ist nun, wenn ich dieecho \'
Bourne-Befehlszeile auf dem Remote-Host ausführen möchte ?echo command-string | ssh ... /bin/sh
ist eine Lösung, die ich in meiner Antwort angegeben habe, aber das bedeutet, dass Sie keine Daten an die Standardeingabe dieses Remote-Befehls senden können.cmd
istcmd=(echo "foo bar")
, sollte die Shell-Befehlszeilessh
so etwas wie "Echo" -Foo-Leiste ". The *first* space (the one before
Echo) is superflous, but doen't harm. The other one (the ones before
" -Foo-Leiste) is needed. With
"% q" Echo, we'd pass a
"-Foo-Leiste" -Befehlszeile sein.Antworten:
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: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 anenv
. 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 habensh
.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 beginntLC_
.In diesen Fällen könnten Sie eine
LC_CODE
Variable haben, die beispielsweise den oben angegebenensh
Code enthält undsh -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):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:
Das ist ein
perl
Wrapper-Skriptssh
. Ich nenne essexec
. Du nennst es so:Also in deinem Beispiel:
Und der Wrapper wird
cmd and its args
zu einer Befehlszeile, die alle Shells als Aufrufcmd
mit ihren Argumenten interpretieren (unabhängig von ihrem Inhalt).Einschränkungen:
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änkungyash
, die Sie ohnehin nicht umgehen können.sh
Außerdem wird davon ausgegangen , dass das ferne System über denprintf
Befehl verfügt.Um zu verstehen, wie es funktioniert, müssen Sie wissen, wie das Zitieren in den verschiedenen Shells funktioniert:
'...'
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:
funktioniert in allen aber:
würde nicht funktionieren
rc
.würde nicht funktionieren
csh
.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.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 ansh
und haben diesesh
erweitert$n
. Dies bedeutet auch, dass zwei Angebotsstufen erforderlich sind, eine für die Remote-Anmeldeshell und eine fürsh
.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 :Dieser Code führt am Ende denselben Befehl aus, wenn er von einer der unterstützten Shells interpretiert wird.
quelle
tl; dr
Lesen Sie die Kommentare, und lesen Sie die andere Antwort , um eine ausführlichere Lösung zu erhalten .
Beschreibung
Nun, meine Lösung funktioniert nicht mit Nicht-
bash
Shells. Aber wenn esbash
am anderen Ende ist, wird es einfacher. Meine Idee ist es,printf "%q"
für die Flucht wiederzuverwenden . Außerdem ist es im Allgemeinen besser lesbar, ein Skript am anderen Ende zu haben, das Argumente akzeptiert. Aber wenn der Befehl kurz ist, ist es wahrscheinlich in Ordnung, ihn einzufügen. Hier sind einige Beispielfunktionen, die in Skripten verwendet werden können:local.sh
:remote.sh
:Die Ausgabe:
Alternativ können Sie
printf
Ihre Arbeit auch selbst erledigen , wenn Sie wissen, was Sie tun:quelle
bash
auf dem Remotecomputer verfügbar ist. Es gibt auch einige Probleme mit fehlenden Anführungszeichen, die Probleme mit Leerzeichen und Platzhaltern verursachen würden.bash
Muscheln. Aber hoffentlich werden die Leute es von Nutzen finden. Ich habe versucht, die anderen Probleme anzusprechen. Sie können mir gerne mitteilen, ob etwas anderes fehlt als das,bash
was ich vermisse .ssh_run user@host "${cmd[@]}"
) immer noch nicht funktioniert . Es fehlen noch einige Anführungszeichen.printf %q
in einem anderen Gebietsschema nicht sicher ist (und auch ziemlich fehlerhaft ist; in Gebietsschemas, die den BIG5-Zeichensatz verwenden, wird sie (4.3.48) in Anführungszeichen gesetztε
alsα`
!). Dazu zitiereshquote()
ich am besten alles und mit einfachen Anführungszeichen nur so wie mit der in meiner Antwort.