Was ist der sauberste Weg, um mehrere Befehle in Bash auszuführen und auszuführen?

349

Ich habe bereits einen SSH-Agenten eingerichtet und kann Befehle auf einem externen Server im Bash-Skript ausführen, indem ich folgende Aufgaben ausführe:

ssh blah_server "ls; pwd;"

Nun möchte ich wirklich viele lange Befehle auf einem externen Server ausführen. All dies zwischen Anführungszeichen zu setzen, wäre ziemlich hässlich, und ich würde es wirklich lieber vermeiden, mehrmals zu ssh'en, nur um dies zu vermeiden.

Gibt es eine Möglichkeit, dies auf einmal in Klammern oder so zu tun? Ich suche etwas in der Art von:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

Grundsätzlich bin ich mit jeder Lösung zufrieden, solange sie sauber ist.

Bearbeiten

Zur Verdeutlichung spreche ich davon, dass dies Teil eines größeren Bash-Skripts ist. Andere müssen sich möglicherweise später mit dem Skript befassen, daher möchte ich es sauber halten. Ich möchte kein Bash-Skript mit einer Zeile haben, die so aussieht:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

weil es extrem hässlich und schwer zu lesen ist.

Eli
quelle
2
Hmm, wie wäre es, all das in ein Skript auf dem Server zu schreiben und es nur mit einem sshAufruf aufzurufen ?
Nikolai Fetissov
@Nikolai wenn die Befehle auf der Client - Seite hängt, können sie in ein Shell - Skript geschrieben werden, dann scp, sshund laufen. Das wird der sauberste Weg, denke ich.
Khachik
6
Dies ist Teil eines größeren Bash-Skripts, daher möchte ich es lieber nicht aufteilen, wenn die Hälfte auf meinem PC und die andere Hälfte auf dem Server lebt und ssh durchläuft. Wenn es überhaupt möglich ist, möchte ich es wirklich nur als ein Skript behalten, das von meinem PC ausgeführt wird. Gibt es wirklich keine saubere Möglichkeit, eine Reihe von Befehlen in ein SSH einzuschließen?
Eli
Der beste Weg ist, nicht Bash zu verwenden, sondern Perl, Python, Ruby usw.
Salva
1
Warum möchten Sie vermeiden, die Remote-Befehle in Anführungszeichen zu setzen? Sie können in den Anführungszeichen Zeilenumbrüche einfügen, so viele Sie möchten. und die Verwendung einer Zeichenfolge anstelle der Standardeingabe bedeutet, dass eine Standardeingabe verfügbar ist, um beispielsweise Eingaben in das Remote-Skript zu lesen. (Unter Unix sind einfache Anführungszeichen normalerweise doppelten Anführungszeichen vorzuziehen, es sei denn, Sie benötigen speziell die lokale Shell, um einige Teile der Zeichenfolge auszuwerten.)
Tripleee

Antworten:

456

Wie wäre es mit einem Bash Here-Dokument :

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

Um die von @Globalz in den Kommentaren erwähnten Probleme zu vermeiden, können Sie möglicherweise (abhängig davon, was Sie auf der Remote-Site tun) die erste Zeile durch ersetzen

ssh otherhost /bin/bash << EOF

Beachten Sie, dass Sie im Here-Dokument eine Variablensubstitution durchführen können, sich jedoch möglicherweise mit Anführungszeichenproblemen befassen müssen. Wenn Sie beispielsweise die "Grenzzeichenfolge" (dh EOFoben) angeben, können Sie keine Variablensubstitutionen durchführen. Ohne Angabe der Grenzwertzeichenfolge werden Variablen ersetzt. Wenn Sie beispielsweise $NAMEoben in Ihrem Shell-Skript definiert haben , können Sie dies tun

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

und es würde eine Datei auf dem Ziel otherhostmit dem Namen dessen erstellen, was Sie zugewiesen hatten $NAME. Es gelten auch andere Regeln für das Zitieren von Shell-Skripten, die jedoch zu kompliziert sind, um hier darauf einzugehen.

Paul Tomblin
quelle
6
Das sieht genau so aus, wie ich es will! Wie funktioniert es Hätten Sie zufällig einen Link zu einer Seite, die dies erklärt?
Eli
9
+1 dachte nur, dass ich - hier ist ein Ort, um darüber zu lesen: tldp.org/LDP/abs/html/here-docs.html
bosmacs
14
Ich erhalte diese Ausgabe auch an mein lokales: Pseudo-Terminal wird nicht zugewiesen, da stdin kein Terminal ist.
Globalz
14
Es kann wichtig sein, dass Sie das Wort (dh das erste 'EOF') zitieren , um eine Erweiterung der Befehlszeilen zu verhindern. Siehe: man bash | weniger + / 'Here Documents'
Versammlung
6
Paul, ich schlage es nur vor, weil es mit fast 200.000 Ansichten zu dieser Frage so aussieht, als würden viele Leute hierher kommen, wenn sie mit ssh schreiben. Es ist üblich, beim Scripting Werte einzufügen. Ohne den Lärm in den anderen Fragen und Kommentaren würde ich einfach einen machen und mich auf den Weg machen, aber es ist unwahrscheinlich, dass er zu diesem Zeitpunkt gesehen wird. Eine einzeilige Fußnote kann den Menschen einige größere Kopfschmerzen ersparen.
Koyae
122

Bearbeiten Sie Ihr Skript lokal und leiten Sie es dann in ssh weiter, z

cat commands-to-execute-remotely.sh | ssh blah_server

wo commands-to-execute-remotely.shsieht aus wie deine Liste oben:

ls some_folder
./someaction.sh
pwd;
bosmacs
quelle
7
Dies hat den großen Vorteil, dass Sie genau wissen , was vom Remote-Skript ausgeführt wird - keine Probleme beim Zitieren. Wenn Sie dynamische Befehle benötigen, können Sie ein Shell-Skript mit einer Subshell verwenden, die weiterhin in das ssh geleitet wird, dh ( echo $mycmd $myvar ; ...) | ssh myhost- wie bei der Verwendung von cat wissen Sie genau, was in den ssh-Befehlsstrom fließt. Und natürlich kann die Unterschale im Skript aus Gründen der Lesbarkeit mehrzeilig sein
RichVel
6
Können Sie dies mit Argumenten in der tun commands-to-execute-remotely.sh?
elaRosca
1
Ich denke, das ist weitaus nützlicher als eine Paultomblin-Lösung. Da das Skript auf jeden SSH-Server umgeleitet werden kann, ohne die Datei öffnen zu müssen. Auch ist es viel besser lesbar.
Patrick Bassut
3
Ja, aber mit Echo oder einem Here-Dokument (siehe obere Antwort): Verwenden Sie $localvar:, um eine lokal definierte Variable \$remotevarzu interpretieren, eine remote definierte Variable remote zu interpretieren \$(something with optionnal args)und die Ausgabe von etwas zu erhalten, das auf dem Remote-Server ausgeführt wird. Ein Beispiel, das Sie durch 1 ssh (oder, wie hier gezeigt, mehrere ssh-Befehle):echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh user1@hop1 ssh user2@hop2 ssh user@finalserver bash
Olivier Dulac
2
Hmm ... wie unterscheidet sich das von der Verwendung der Eingangsumleitung, dh ssh blah_server < commands-to-execute-remotely.sh?
Flow2k
41

Um Ihrem Beispielcode zu entsprechen, können Sie Ihre Befehle in einfache oder doppelte Qoutes einschließen. Zum Beispiel

ssh blah_server "
  ls
  pwd
"
Andrei B.
quelle
Ich mag dieses Format, aber es ist leider nicht nützlich, um Standarddaten in einer Variablen zu speichern.
Signus
1
Signus, was meinst du mit "Speichern von Standarddaten in einer Variablen"?
Andrei B
1
@Signus Es ist durchaus möglich, das zu tun, was Sie beschreiben, obwohl Sie wahrscheinlich einfache Anführungszeichen anstelle von doppelten Anführungszeichen um die Remote-Befehle verwenden möchten (oder den Operatoren entkommen möchten, die in doppelte Anführungszeichen eingeschlossen werden müssen, um zu verhindern, dass Ihre lokale Shell abfängt und sie interpolieren).
Tripleee
Ich kann keinen Befehl wie diesen fileToRemove = $ in doppelten Anführungszeichencode einfügen (find. -Type f -name 'xxx.war'). fileToRemove sollte einen Dateinamen enthalten, aber stattdessen eine leere Zeichenfolge. Muss etwas entkommen?
Jkonst
37

Ich sehe zwei Möglichkeiten:

Zuerst machen Sie eine Steuerbuchse wie folgt:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

und führen Sie Ihre Befehle aus

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

Auf diese Weise können Sie einen ssh-Befehl schreiben, ohne die Verbindung zum Server wiederherzustellen.

Die zweite Möglichkeit besteht darin, das Skript dynamisch zu generieren, scpauszuführen und auszuführen.

Ende
quelle
20

Dies kann auch wie folgt erfolgen. Fügen Sie Ihre Befehle in ein Skript ein, nennen wir es command-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

Speicher die Datei

Führen Sie es nun auf dem Remote-Server aus.

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

Für mich nie gescheitert.

RJ
quelle
Ähnlich wie ich ursprünglich dachte! Aber warum wird bash -sgebraucht?
Flow2k
Auch wird #!/bin/bashwirklich verwendet?
Flow2k
2
Das -s dient der Kompatibilität. From man bash -s Wenn die Option -s vorhanden ist oder nach der Optionsverarbeitung keine Argumente mehr vorhanden sind, werden Befehle aus der Standardeingabe gelesen. Mit dieser Option können die Positionsparameter beim Aufrufen einer interaktiven Shell festgelegt werden.
RJ
1
RJ, danke! Bei meinem ersten Kommentar sieht es so aus, als ob wir es gerade tun ssh user@remote < /path/to/commands-inc.sh(es scheint für mich zu funktionieren). Nun, ich denke, Ihre Version stellt sicher, dass wir die bashShell verwenden und nicht irgendeine andere Shell - das ist der Zweck, nicht wahr?
Flow2k
2
Vielen Dank. Ja, einige von uns haben ihre Notizen und Gewohnheiten aus älteren Versionen von Bash und anderen Muscheln. :-)
RJ
10

Setzen Sie alle Befehle in ein Skript ein und es kann wie folgt ausgeführt werden

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh
Jai Prakash
quelle
Etwas seltsam, aber es funktioniert gut. Und Argumente können an das Skript übergeben werden (entweder vor oder nach der Umleitung). zB 'ssh <remote-user> @ <remote-host> "bash -s" arg1 <./ remote-commands.sh arg2' zB 'ssh <remote-user> @ <remote-host> "bash -s" - - -arg1 <./ remote-commands.sh arg2 '
gaoithe
Ich hatte eine ähnliche Frage mit einer anderen Antwort oben, aber was ist der Zweck der Aufnahme bash -s?
Flow2k
1
@ flow2k -s gibt die Bash an, mit der die Befehle von der Standardeingabe gelesen werden sollen. Hier ist die Beschreibung von den manSeitenIf the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.
Jai Prakash
@JaiPrakash Danke dafür, aber ich dachte tatsächlich, ob wir den bashBefehl einfach ganz abschaffen können , dh ssh remote-user@remote-host <./remote-commands.sh. Ich habe es versucht und es schien zu funktionieren, obwohl ich nicht sicher bin, ob es in irgendeiner Weise mangelhaft ist.
Flow2k
@ flow2k Nach meinem Verständnis der Manpages wird es ohne diese Option keinen Mangel geben. Dies ist erforderlich, wenn Sie versuchen, Argumente weiterzugeben. - Danke
Jai Prakash
9

SSH und mehrere Befehle in Bash ausführen.

Separate Befehle mit Semikolons innerhalb einer Zeichenfolge, die an echo übergeben werden und alle in den Befehl ssh geleitet werden. Zum Beispiel:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
Arnab
quelle
arnab, bitte beschreiben Sie in Zukunft kurz, was Ihr Code tut. Sprechen Sie dann darüber, wie es funktioniert, und geben Sie den Code ein. Fügen Sie dann den Code in einen Codeblock ein, damit er leicht zu lesen ist.
Eric Leschinski
Aus der Frage haben Sie genau das angeboten , was das OP vermeiden möchte: "Ich möchte kein Bash-Skript mit einer Zeile haben, die aussieht wie ..."
jww
6

Für alle, die hier wie ich stolpern - ich hatte Erfolg damit, dem Semikolon und der Newline zu entkommen:

Erster Schritt: das Semikolon. Auf diese Weise wird der Befehl ssh nicht unterbrochen:

ssh <host> echo test\;ls
                    ^ backslash!

Listet das Remote-Hosts / Home-Verzeichnis auf (als Root angemeldet)

ssh <host> echo test;ls
                    ^ NO backslash

listet das aktuelle Arbeitsverzeichnis auf.

Nächster Schritt: Aufbrechen der Linie:

                      v another backslash!
ssh <host> echo test\;\
ls

Dies listete erneut das Remote-Arbeitsverzeichnis auf - verbesserte Formatierung:

ssh <host>\
  echo test\;\
  ls

Wenn wirklich schöner als hier Dokument oder Zitate um gestrichelte Linien - na ja, nicht ich zu entscheiden ...

(Mit bash Ubuntu 14.04 LTS.)

Aconcagua
quelle
1
Was noch besser ist, können Sie && und || verwenden auch auf diese Weise - Echo-Test \ & \ & ls
Miro Kropacek
@MiroKropacek Das ist auch schön. Besser? Kommt darauf an, was du willst; Führen Sie den zweiten Befehl bedingt aus , dann ja, führen Sie ihn bedingungslos aus (egal ob der erste erfolgreich war oder fehlgeschlagen ist), dann nicht ...
Aconcagua
@MiroKropacek, ich musste mich nicht dem && entziehen, wenn ich die Befehle in doppelte Anführungszeichen setzte:ssh host "echo test && ls"
not2savvy
5

Die veröffentlichten Antworten mit mehrzeiligen Zeichenfolgen und mehreren Bash-Skripten haben bei mir nicht funktioniert.

  • Lange mehrzeilige Zeichenfolgen sind schwer zu pflegen.
  • Separate Bash-Skripte verwalten keine lokalen Variablen.

Hier ist eine funktionale Möglichkeit, mehrere Befehle zu ssh und auszuführen, während der lokale Kontext beibehalten wird.

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"
Michael Petrochuk
quelle
3
Sie denken darüber nach, als ob der einzige Grund, warum jemand dies tun möchte, darin besteht, dass er an einer Befehlszeile sitzt. Es gibt auch andere häufige Gründe für diese Frage, wie das Ausführen von Befehlen in verteilten Umgebungen mit Tools wie Rundeck, Jenkins usw.
Charles Addis,
Dies funktioniert, aber ich
erhalte
5

Nicht sicher, ob das sauberste für lange Befehle, aber sicherlich das einfachste:

ssh user@host "cmd1; cmd2; cmd3"
DimiDak
quelle
Beste Einfachheit!
not2savvy
4

Dies funktioniert gut zum Erstellen von Skripten, da Sie keine anderen Dateien einschließen müssen:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF
sjas
quelle
Der Teil "$ (which bash) -s" gibt die Position von bash auf dem lokalen Computer und nicht auf dem Remote-Computer an. Ich denke, Sie möchten stattdessen '$ (which bash) -s' (einfache Anführungszeichen, um die Substitution lokaler Parameter zu unterdrücken).
jsears
1
Paul Tomblin gab 6 Jahre zuvor die Antwort auf das Bash Here-Dokument . Welchen Mehrwert bietet diese Antwort?
JWW
-sflag, ich zitiere ihn, dass du vielleicht davon kommst, die erste Zeile durch ... zu ersetzen , und ich konnte nicht davonkommen, nur Bash zu rufen, an das ich mich nur schwach erinnere.
Sjas
4

Der einfachste Weg, Ihr System so zu konfigurieren, dass standardmäßig einzelne SSH-Sitzungen mit Multiplexing verwendet werden.

Dies kann durch Erstellen eines Ordners für die Sockets erfolgen:

mkdir ~/.ssh/controlmasters

Fügen Sie dann Ihrer .ssh-Konfiguration Folgendes hinzu:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

Jetzt müssen Sie keinen Code mehr ändern. Dies ermöglicht mehrere Aufrufe von ssh und scp, ohne mehrere Sitzungen zu erstellen. Dies ist hilfreich, wenn mehr Interaktion zwischen Ihren lokalen und Remote-Computern erforderlich ist.

Dank der Antwort von @ terminus, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ und https://en.wikibooks.org / wiki / OpenSSH / Kochbuch / Multiplexing .

Jonathan
quelle