Bash: Interaktive Remote-Eingabeaufforderung

16

Ich habe ein Skript, das eine Verbindung zu einem Remote-Server herstellt und prüfe, ob ein Paket installiert ist:

ssh root@server 'bash -s' < myscript.sh

myscript.sh:

OUT=`rpm -qa | grep ntpdate`
if [ "$OUT" != "" ] ; then
    echo "ntpdate already installed"
else
    yum install $1
fi

Dieses Beispiel könnte vereinfacht werden. Hier ist myscript2.shdas gleiche Problem:

read -p "Package is not installed. Do you want to install it (y/n)?" choise

Mein Problem ist, dass Bash meine Antworten nicht interaktiv lesen kann.

Gibt es eine Möglichkeit, lokales Skript remote auszuführen, ohne die Möglichkeit zu verlieren, Benutzer dazu aufzufordern?

Anthony Ananich
quelle
Können Sie näher darauf eingehen? Es ist nicht wirklich klar, wonach Sie fragen? Etwas mehr von Ihrem Code wäre hilfreich.
slm
1
Der Grund, warum dies nicht funktioniert, ist, dass Sie Ihr Skript über STDIN übergeben. Wenn die Bash zum Lesen von STDIN geht, erhält sie Ihr Skript (oder nichts, da bereits das gesamte Skript gelesen wurde und nichts mehr übrig ist).
Patrick
1
Nützliche Informationen
slm

Antworten:

22

Versuchen Sie so etwas:

$ ssh -t yourserver "$(<your_script)"

Das -terzwingt eine tty-Zuweisung, $(<your_script)liest die gesamte Datei und übergibt in diesem Fall den Inhalt als ein Argument an ssh, das von der Shell des Remote-Benutzers ausgeführt wird.

Wenn das Skript Parameter benötigt, übergeben Sie diese nach dem Skript:

$ ssh -t yourserver "$(<your_script)" arg1 arg2 ...

Funktioniert für mich, ich bin mir nicht sicher, ob es universell ist.

Matte
quelle
3
Genau das habe ich gesucht, danke!
Anthony Ananich
Gute Lösung, aber gibt es eine Möglichkeit, ein Argument zu übergeben your script, während die Vorteile der in Ihrer Antwort verwendeten Syntax beibehalten werden? Mit ssh -t yourserver "$(<your_script --some_arg)"nur Ergebnissen in bash: --some_arg: command not found.
sampablokuper
1
@sampablokuper: versuchenssh ... $(<script) script_arg1 script_arg2 ...
Mat
@ Mat, danke, WFM :) Vielleicht füge es dem Text deiner Antwort hinzu?
sampablokuper
3

Ihr Problem besteht darin, dass ssheine nicht interaktive Anmeldeshell auf dem Remotecomputer gestartet wird. Die naheliegende einfache Lösung wäre, das Skript auf den Remote-Server zu kopieren und von dort aus auszuführen:

scp myscript.sh root@server:/tmp && ssh root@server /tmp/myscript.sh

Wenn das Kopieren aus irgendeinem Grund nicht möglich ist, ändere ich das Skript, um zunächst eine Verbindung herzustellen und zu überprüfen, ob $1es installiert ist, und stelle die Verbindung dann wieder her und installiere es nach Bedarf:

OUT=$(ssh root@server rpm -qa | grep "$1");
if [ "$OUT" != "" ] ; then
    echo "$1 already installed"
else
   read -p "Package $1 is not installed. Do you want to install it (y/n)?" choice
   if [ "$choice" -eq "y" ]; then
       ssh root@server yum install "$1"
   fi
fi
terdon
quelle
Davor hatte ich Angst. Die Verbindung zu ssh ist die längste Operation und ich habe versucht, sie zu vermeiden.
Anthony Ananich
@AnthonyAnanich Sie können die Zeit für den Verbindungsaufbau sparen, indem Sie eine Master-Verbindung einrichten .
Gilles 'SO- hör auf böse zu sein'
@ Gilles Ich dachte darüber nach, dies vorzuschlagen, aber ich dachte, es würde mit meinem Vorschlag nicht funktionieren. Wenn die erste SSH-Verbindung als Master fungiert, wird sie nach dem $OUT=$(ssh root@server rpm -qa | grep "$1");Beenden geschlossen, und die zweite Verbindung dauert genauso lange wie die erste. Liege ich falsch?
terdon
1
@terdon Führe so etwas wie ssh -M foo.example.com sleep 99999999oder ssh -M foo.example.com read <somefifoals Master aus und töte es explizit (mit killoder echo done >somefifo), wenn du fertig bist.
Gilles 'SO - hör auf, böse zu sein'
0

Hier ist eine gute Erklärung .

Also habe ich das Skript angepasst

hostname
echo -n "Make your choice :"
read choice
echo "You typed " ${choice}
echo done

und das hat nicht funktioniert.

Deshalb habe ich das Skript auf die Fernbedienung verschoben, um die lokale Umleitung auf ssh zu vermeiden. (Meine Befehle befinden sich in einer Datei mit dem Namen f )

cat f | ssh user@remotehost.com 'cat >remf'
ssh user@remotehost bash remf

Das hat funktioniert. Hier ist die Ausgabe:

christian@clafujiu:~/tmp$ ssh localhost bash tmp/f
christian@localhost's password: 
Linux clafujiu 2.6.32-52-generic #114-Ubuntu SMP Wed Sep 11 19:00:15 UTC 2013 i686 GNU/Linux
Sun Nov 10 14:58:56 GMT 2013
Make your choice :abc
You typed  abc
done

Da @terdon erwähnte, dass die ursprüngliche Absicht darin bestand, lokale Skripte aus der Ferne auszuführen, kann die Fernkopie automatisiert werden. Dies ist nur ein Beispiel für eine einzige Zeile.

REMID=`cat f |ssh user@remotehost 'cat > remf_$$; echo $$'` ;ssh root@redtoadservices.com "bash remf_${REMID} ; rm -v remf_${REMID}"
X Tian
quelle
2
Wie hat es "funktioniert"? Haben Sie dieses Skript bash -s < script.shwie das OP ausgeführt? Könnten Sie die Erklärung in Ihre Antwort aufnehmen, anstatt sie zu verlinken?
Terdon
Tut mir leid, dass ich vergessen habe, etwas Kritisches hinzuzufügen. Ich habe das Skript auch auf die Fernbedienung kopiert, um die lokale Umleitung zu vermeiden. Ich werde dies an meine Antwort anhängen, um es klar zu machen.
X Tian
1
Wenn Sie das Skript auf die Fernbedienung kopieren, wird das Problem behoben. Das Problem des OP ist, dass er versucht, einem lokalen Skript, das remote ausgeführt wird, eine interaktive Eingabe zu geben . Natürlich wird es von dir kopiert. Das OP möchte dieses Skript ausführen, wenn eine Verbindung zu einem Remote-Server hergestellt wird, vermutlich zu vielen Remote-Servern. Ich bezweifle, dass es praktisch ist, es zu kopieren, wenn Sie es nicht automatisieren.
Terdon
0

Ich habe in der Vergangenheit mehrere Male nach Lösungen für dieses Problem gesucht, jedoch nie eine völlig zufriedenstellende gefunden. Wenn Sie in ssh umleiten, verlieren Sie Ihre Interaktivität. Zwei Verbindungen (scp / ssh) sind langsamer und Ihre temporäre Datei kann herumliegen. Und das ganze Skript auf der Kommandozeile endet oft in der Flucht vor der Hölle.

Kürzlich habe ich festgestellt, dass der Befehlszeilenpuffer normalerweise ziemlich groß ist ('getconf ARG_MAX> 2MB, wo ich nachgeschaut habe). Und das brachte mich zum Nachdenken darüber, wie ich dies nutzen und das entstehende Problem lindern könnte.

Das Ergebnis ist:

ssh -t <host> /bin/bash "<(echo "$(cat my_script | base64 | tr -d '\n')" | base64 --decode)" <arg1> ...

oder mit einem Here-Dokument und einer Katze:

ssh -t <host> /bin/bash $'<(cat<<_ | base64 --decode\n'$(cat my_script | base64)$'\n_\n)' <arg1> ...

Ich habe diese Idee erweitert, um ein voll funktionsfähiges BASH-Beispielskript zu erstellen sshx, das beliebige Skripten (nicht nur BASH) ausführen kann, wobei Argumente auch lokale Eingabedateien sein können, und zwar über ssh. Sehen Sie hier .

SourceSimian
quelle
Interessant, aber wie und / oder wann ist das besser als Mats Antwort ?
Scott,
1
Die Antwort von Mat ist ein guter erster Durchgang (den ich oft verwende), der jedoch kein Skript mit willkürlichem Inhalt verarbeiten kann, z. B. "Zeichen, die maskiert werden müssen. Und es ist auch auf BASH-Skripte beschränkt. Meine Lösung ist ein allgemeiner Fall für jeden Skriptinhalt einer installierten Skriptsprache. Außerdem demonstriere ich in sshx die Serialisierung von Argumentdateien, was ziemlich geschickt ist.
SourceSimian
(1) Können Sie ein MCVE eines Skripts veröffentlichen, für das die Antwort von Mat fehlschlägt (und Ihre funktioniert)? (2) Siehe Was ist los mit "echo $ (stuff)" oder "echo` stuff` "?  Ihr echo "$(cat my_script | base64)" | base64 --decodeAussehen Äquivalent (ish) zu cat my_script | base64 | base64 --decode, die viel wie ein No-op aussieht.
Scott
Hallo @Scott. (1): Betrachten Sie das Skript: echo "ARG1=$1, USER=$USER, END". a) $ ssh host "$(<my_bash)" foo-> ARG1=, USER=user, END foo. b) $ ssh host bash "<(echo $(cat my_bash|base64) | base64 --decode)" foo-> ARG1=foo, USER=user, END. Hier läuft (a) effektiv: bash -c $'#!/bin/bash\necho "ARG1=$1, USER=$USER, END" foo'oder ähnliches. Beachten Sie, dass das Argument nicht funktioniert. Wo (b) ausgeführt wird : bash <(echo IyEvY...5EIgo= | base64 --decode) foo. (2) Ich habe base64 als Transportkodierung verwendet.
SourceSimian