Quel est le moyen le plus propre de ssh et d'exécuter plusieurs commandes dans Bash?

349

J'ai déjà un agent ssh configuré et je peux exécuter des commandes sur un serveur externe en script Bash en faisant des choses comme:

ssh blah_server "ls; pwd;"

Maintenant, ce que j'aimerais vraiment faire, c'est exécuter beaucoup de longues commandes sur un serveur externe. Mettre tout cela entre guillemets serait assez moche, et je préfère vraiment éviter de faire plusieurs fois juste pour éviter cela.

Alors, existe-t-il un moyen de le faire en une seule fois entre parenthèses ou quelque chose? Je cherche quelque chose dans le sens de:

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

Fondamentalement, je serai satisfait de toute solution tant qu'elle est propre.

Éditer

Pour clarifier, je parle de faire partie d'un script bash plus large. D'autres personnes pourraient avoir besoin de gérer le script sur toute la ligne, donc je voudrais le garder propre. Je ne veux pas avoir un script bash avec une seule ligne qui ressemble à ceci:

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

car il est extrêmement moche et difficile à lire.

Eli
la source
2
Hmm, que diriez-vous de mettre tout cela dans un script sur le serveur et de simplement l'appeler avec une seule sshinvocation?
Nikolai Fetissov
@Nikolai si les commandes dépendent du côté client, ils peuvent écrire dans un script shell, puis scp, sshet courir. Ce sera la façon la plus propre, je pense.
khachik
6
Cela fait partie d'un script bash plus grand, donc je préfère ne pas le diviser avec la moitié vivant sur mon ordinateur personnel et l'autre moitié vivant sur le serveur et exécutant via ssh. Si possible, j'aimerais vraiment le garder comme un script exécuté à partir de mon ordinateur personnel. N'y a-t-il vraiment aucun moyen propre de mettre un tas de commandes dans un ssh?
Eli
Le meilleur moyen n'est pas d'utiliser bash mais Perl, Python, Ruby, etc.
salva
1
Pourquoi voulez-vous éviter de mettre les commandes à distance entre guillemets? Vous pouvez avoir des retours à la ligne à l'intérieur des guillemets, autant que vous le souhaitez; et l'utilisation d'une chaîne au lieu d'une entrée standard signifie qu'une entrée standard est disponible, par exemple pour lire une entrée dans le script distant. (Bien que sous Unix, les guillemets simples soient généralement préférés aux guillemets doubles, sauf si vous avez spécifiquement besoin du shell local pour évaluer certaines parties de la chaîne.)
tripleee

Réponses:

457

Que diriez-vous d'un document Bash Here :

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

Pour éviter les problèmes mentionnés par @Globalz dans les commentaires, vous pourrez peut-être (en fonction de ce que vous faites sur le site distant) éviter de remplacer la première ligne par

ssh otherhost /bin/bash << EOF

Notez que vous pouvez effectuer une substitution de variable dans le document Ici, mais vous devrez peut-être traiter des problèmes de citation. Par exemple, si vous citez la "chaîne limite" (c'est-à-dire EOFdans ce qui précède), vous ne pouvez pas faire de substitutions de variables. Mais sans citer la chaîne limite, les variables sont substituées. Par exemple, si vous avez défini $NAMEci-dessus dans votre script shell, vous pouvez faire

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

et cela créerait un fichier sur la destination otherhostavec le nom de tout ce que vous auriez attribué $NAME. D'autres règles concernant les citations de scripts shell s'appliquent également, mais sont trop compliquées pour être abordées ici.

Paul Tomblin
la source
6
Cela ressemble exactement à ce que je veux! Comment ça marche? Auriez-vous un lien vers une page qui l'explique?
Eli
9
+1 Je pensais juste que moi-même - voici un endroit pour lire à ce sujet: tldp.org/LDP/abs/html/here-docs.html
bosmacs
14
J'obtiens également cette sortie à mon local: le pseudo-terminal ne sera pas alloué car stdin n'est pas un terminal.
Globalz
14
Il peut être important pour vous de citer le mot (c'est-à-dire le premier «EOF») afin d'empêcher l'expansion des lignes de commande. Voir: homme bash | moins + / 'Here Documents'
assem
6
Paul, je ne le suggère que parce qu'avec près de 200 000 vues sur cette question, il semble que beaucoup de gens viennent ici lors de l'écriture de scripts avec ssh. Il est courant d'avoir besoin d'injecter des valeurs lors de l'écriture de scripts. Sinon pour le bruit dans les autres questions et commentaires, je voudrais juste en faire une et être sur mon chemin, mais il est peu probable d'être vu à ce stade. Une note de bas de page sur une ligne pourrait éviter aux gens des maux de tête majeurs.
koyae
123

Modifiez votre script localement, puis canalisez-le dans ssh, par exemple

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

commands-to-execute-remotely.shressemble à votre liste ci-dessus:

ls some_folder
./someaction.sh
pwd;
bosmacs
la source
7
Cela a le grand avantage de savoir exactement ce qui est exécuté par le script distant - aucun problème avec les guillemets. Si vous avez besoin de commandes dynamiques, vous pouvez utiliser un script shell avec un sous-shell, toujours dans le ssh, c'est ( echo $mycmd $myvar ; ...) | ssh myhost-à- dire - comme pour l'utilisation de cat, vous savez exactement ce qui se passe dans le flux de commandes ssh. Et bien sûr, le sous-shell dans le script peut être multiligne pour plus de lisibilité - voir linuxjournal.com/content/bash-sub-shells
RichVel
6
Pouvez-vous faire cela avec des arguments dans le commands-to-execute-remotely.sh?
elaRosca
1
Je pense que c'est beaucoup plus utile que la solution paultomblin. Puisque le script peut être redirigé vers n'importe quel serveur ssh sans avoir à ouvrir le fichier. C'est aussi beaucoup plus lisible.
Patrick Bassut
3
oui, mais en utilisant echo ou un document here (voir la réponse du haut): utiliser: $localvarpour interpréter une variable définie localement, \$remotevarpour interpréter à distance une variable définie à distance, \$(something with optionnal args)pour obtenir la sortie de quelque chose exécuté sur le serveur distant. Un exemple que vous pouvez passer de ssh à 1 (ou, comme illustré ici, plusieurs commandes ssh):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 ... en quoi est-ce différent de l'utilisation de la redirection d'entrée, c'est ssh blah_server < commands-to-execute-remotely.sh-à- dire ?
flow2k
41

Pour correspondre à votre exemple de code, vous pouvez encapsuler vos commandes dans des qoutes simples ou doubles. Par exemple

ssh blah_server "
  ls
  pwd
"
Andrei B
la source
J'aime ce format, mais il n'est malheureusement pas utile pour stocker des données standard dans une variable.
Signus
1
Signus, que voulez-vous dire par "stocker des données std dans une variable"?
Andrei B
1
@Signus Il est parfaitement possible de faire ce que vous décrivez, bien que vous souhaiterez probablement utiliser des guillemets simples au lieu de guillemets doubles autour des commandes distantes (ou échapper les opérateurs qui doivent être échappés à l'intérieur des guillemets doubles pour empêcher votre shell local d'intercepter et de les interpoler).
tripleee
Je ne suis pas en mesure de mettre dans le code de guillemets doubles une commande comme celle-ci fileToRemove = $ (find. -Type f -name 'xxx.war'). fileToRemove devrait avoir un nom de fichier à l'intérieur mais à la place il a une chaîne vide. Faut-il échapper à quelque chose?
jkonst
37

Je vois deux façons:

Vous créez d'abord une prise de contrôle comme celle-ci:

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

et exécutez vos commandes

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

De cette façon, vous pouvez écrire une commande ssh sans vous reconnecter réellement au serveur.

La seconde consisterait à générer dynamiquement le script, à le scplancer et à l'exécuter.

terminus
la source
20

Cela peut également être fait comme suit. Mettez vos commandes dans un script, appelons-le commandes-inc.sh

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

Sauvegardez le fichier

Maintenant, exécutez-le sur le serveur distant.

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

Jamais échoué pour moi.

RJ
la source
Semblable à ce que je pensais à l'origine! Mais pourquoi est-il bash -snécessaire?
flow2k
Aussi, est-il #!/bin/bashvraiment utilisé?
flow2k
2
Le -s est là pour la compatibilité. From man bash -s Si l'option -s est présente ou s'il ne reste aucun argument après le traitement de l'option, les commandes sont lues à partir de l'entrée standard. Cette option permet de définir les paramètres positionnels lors de l'appel d'un shell interactif.
RJ
1
RJ, merci! Avec mon premier commentaire, il semble que nous venons de le faire ssh user@remote < /path/to/commands-inc.sh(cela semble fonctionner pour moi). Eh bien, je suppose que votre version garantit que nous utilisons le bashshell, et non un autre shell - c'est le but, n'est-ce pas?
flow2k
2
Merci. Oui, certains d'entre nous ont nos notes et nos habitudes des anciennes versions de bash et d'autres coquilles. :-)
RJ
10

Mettez toutes les commandes dans un script et il peut être exécuté comme

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh
Jai Prakash
la source
Un peu bizarre mais ça marche bien. Et les arguments peuvent être passés au script (avant ou après la redirection). par exemple 'ssh <remote-user> @ <remote-host> "bash -s" arg1 <./ remote-commands.sh arg2' eg 'ssh <remote-user> @ <remote-host> "bash -s" - - -arg1 <./ remote-commands.sh arg2 '
gaoithe
J'ai eu une question similaire avec une autre réponse ci-dessus, mais quel est le but d'inclure bash -s?
flow2k
1
@ flow2k -s est pour indiquer le bash pour lire les commandes depuis l'entrée standard. Voici la description des manpagesIf 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 Merci pour cela, mais je pensais en fait si nous pouvions simplement supprimer bashcomplètement la commande, c'est-à-dire ssh remote-user@remote-host <./remote-commands.sh. J'ai essayé mon chemin et cela a semblé fonctionner, bien que je ne sois pas sûr qu'il soit déficient d'une manière ou d'une autre.
flow2k
@ flow2k d'après ma compréhension des pages de manuel, il n'y aura aucune lacune sans cette option. Son requis si vous essayez de transmettre des arguments. - merci
Jai Prakash
9

SSH et exécutez plusieurs commandes dans Bash.

Commandes séparées avec des points-virgules dans une chaîne, passées à echo, toutes canalisées dans la commande ssh. Par exemple:

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
la source
arnab, à l'avenir, veuillez décrire brièvement ce que fait votre code. Ensuite, parlez de son fonctionnement, puis insérez le code. Ensuite, placez le code dans un bloc de code afin qu'il soit facile à lire.
Eric Leschinski
De la question, vous avez proposé exactement ce que l'OP veut éviter: "Je ne veux pas avoir un script bash avec une ligne qui ressemble à ..."
jww
6

Pour tous ceux qui trébuchent ici comme moi - j'ai réussi à échapper au point-virgule et à la nouvelle ligne:

Première étape: le point-virgule. De cette façon, nous ne cassons pas la commande ssh:

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

Répertorie le répertoire hôtes / home distant (connecté en tant que root), tandis que

ssh <host> echo test;ls
                    ^ NO backslash

répertorié le répertoire de travail actuel.

Étape suivante: rompre la ligne:

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

Cela a de nouveau répertorié le répertoire de travail distant - formatage amélioré:

ssh <host>\
  echo test\;\
  ls

Si c'est plus joli qu'ici, documentez ou citez autour de lignes brisées - enfin, pas moi de décider ...

(En utilisant bash, Ubuntu 14.04 LTS.)

Aconcagua
la source
1
Quoi de mieux que vous pouvez utiliser && et || de cette façon aussi - test d'écho \ & \ & ls
Miro Kropacek
@MiroKropacek C'est bien aussi. Meilleur? Cela dépend de ce que vous voulez; exécuter la deuxième commande conditionnellement , alors oui, l'exécuter sans condition (peu importe si la première a réussi ou échoué), alors non ...
Aconcagua
@MiroKropacek, je n'ai pas eu à échapper au && lorsque j'ai mis des guillemets autour des commandes:ssh host "echo test && ls"
not2savvy
5

Les réponses publiées utilisant des chaînes multilignes et plusieurs scripts bash ne fonctionnaient pas pour moi.

  • Les longues chaînes multilignes sont difficiles à entretenir.
  • Les scripts bash séparés ne conservent pas de variables locales.

Voici un moyen fonctionnel de ssh et d'exécuter plusieurs commandes tout en conservant le contexte local.

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
la source
3
Vous pensez à cela comme si la seule raison pour laquelle quelqu'un voudrait faire cela était s'il était assis sur une ligne de commande. Il existe également d'autres raisons courantes à cette question, telles que l'exécution de commandes dans des environnements distribués avec des outils tels que rundeck, jenkins, etc.
Charles Addis
cela fonctionne, mais je reçois des messages d'erreur indésirables
Annahri
5

Je ne sais pas si le plus propre pour les commandes longues mais certainement le plus simple:

ssh user@host "cmd1; cmd2; cmd3"
DimiDak
la source
Le meilleur de la simplicité!
not2savvy
4

Cela fonctionne bien pour créer des scripts, car vous n'avez pas besoin d'inclure d'autres fichiers:

#!/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
la source
La partie "$ (which bash) -s" vous donne l'emplacement de bash sur la machine locale, plutôt que sur la machine distante. Je pense que vous voulez '$ (which bash) -s' à la place (guillemets simples pour supprimer la substitution de paramètres locaux).
jsears
1
Paul Tomblin a fourni la réponse du document Bash Here 6 ans plus tôt. Quelle valeur ajoutée cette réponse?
2016 à 8h21
-sflag, je le cite, vous pourrez peut-être vous en sortir en remplaçant la première ligne par ... , et je n'ai pas pu m'en tirer en appelant simplement bash, je m'en souviens vaguement.
sjas
4

La façon la plus simple de configurer votre système pour utiliser des sessions ssh uniques par défaut avec le multiplexage.

Cela peut être fait en créant un dossier pour les sockets:

mkdir ~/.ssh/controlmasters

Et puis en ajoutant ce qui suit à votre configuration .ssh:

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

Maintenant, vous n'avez plus besoin de modifier aucun de vos codes. Cela permet plusieurs appels à ssh et scp sans créer plusieurs sessions, ce qui est utile lorsqu'il doit y avoir plus d'interaction entre vos machines locales et distantes.

Merci à la réponse de @ terminus, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ et https://en.wikibooks.org / wiki / OpenSSH / Cookbook / Multiplexing .

Jonathan
la source