web-dev-qa-db-fra.com

Utilisation de la boucle while pour ssh vers plusieurs serveurs

J'ai un fichier servers.txt, avec liste des serveurs:

server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

quand je lis le fichier ligne par ligne avec while et que je fais écho à chaque ligne, tout fonctionne comme prévu. Toutes les lignes sont imprimées.

$ while read Host ; do echo $Host ; done < servers.txt
server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

Cependant, quand je veux ssh sur tous les serveurs et exécuter une commande, soudainement ma boucle while cesse de fonctionner:

$ while read Host ; do ssh $Host "uname -a" ; done < servers.txt
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

Cela se connecte uniquement au premier serveur de la liste, pas à tous. Je ne comprends pas ce qui se passe ici. Quelqu'un peut-il expliquer?

C'est encore plus étrange, car l'utilisation de la boucle for fonctionne très bien:

$ for Host in $(cat servers.txt ) ; do ssh $Host "uname -a" ; done
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server2 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server3 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

Ce doit être quelque chose de spécifique à ssh, car d'autres commandes fonctionnent bien, comme ping:

$ while read Host ; do ping -c 1 $Host ; done < servers.txt
82
Martin Vegter

ssh lit le reste de votre entrée standard.

while read Host ; do … ; done < servers.txt

read lit depuis stdin. Le < redirige stdin depuis un fichier.

Malheureusement, la commande que vous essayez d'exécuter lit également stdin, donc elle finit par manger le reste de votre fichier. Vous pouvez le voir clairement avec:

$ while read Host ; do echo start $Host end; cat; done < servers.txt 
start server1.mydomain.com end
server2.mydomain.com
server3.mydomain.com

Remarquez comment cat a mangé (et fait écho) les deux lignes restantes. (Si la lecture avait été effectuée comme prévu, chaque ligne aurait le "début" et la "fin" autour de l'hôte.)

Pourquoi for fonctionne-t-il?

Votre ligne for ne redirige pas vers stdin. (En fait, il lit l'intégralité du contenu du servers.txt fichier en mémoire avant la première itération). Donc ssh continue de lire son stdin depuis le terminal (ou peut-être rien, selon la façon dont votre script est appelé).

Solution

Au moins en bash, vous pouvez avoir read utiliser un descripteur de fichier différent.

while read -u10 Host ; do ssh $Host "uname -a" ; done 10< servers.txt
#          ^^^^                                       ^^

devrait fonctionner. 10 est juste un numéro de fichier arbitraire que j'ai choisi. 0, 1 et 2 ont des significations définies, et généralement l'ouverture des fichiers commence à partir du premier nombre disponible (donc 3 est le prochain à être utilisé). 10 est donc suffisamment haut pour rester à l'écart, mais suffisamment bas pour être sous la limite dans certains obus. De plus, c'est un joli numéro rond ...

Solution alternative 1: -n

Comme McNisse le souligne dans sa réponse , le client OpenSSH a un -n option qui l'empêchera de lire stdin. Cela fonctionne bien dans le cas particulier de ssh, mais bien sûr d'autres commandes peuvent en manquer - les autres solutions fonctionnent quelle que soit la commande qui mange votre stdin.

Solution alternative 2: deuxième redirection

Vous pouvez apparemment (comme dans, je l'ai essayé, cela fonctionne dans ma version de Bash au moins ...) faire une deuxième redirection, qui ressemble à ceci:

while read Host ; do ssh $Host "uname -a" < /dev/null; done < servers.txt

Vous pouvez l'utiliser avec n'importe quelle commande, mais ce sera difficile si vous voulez réellement que l'entrée du terminal passe à la commande.

102
derobert

Comme le décrit Derobert ssh lit votre stdin.

Pour modifier ce comportement, vous pouvez ajouter -n no ssh pour l'empêcher de lire stdin.

ssh -n $Host "uname -a"
33
McNisse

Vous feriez probablement mieux d'utiliser pssh du projet parallel-ssh .

pssh -h $hostfile -t $timeout -i $commands

-i signifie interactif. pssh est également livré avec un scp parallèle et rsync parallèle. Ce qui est bien, c'est qu'il s'exécute de manière asynchrone et exécutera autant de threads que vous le lui demandez. La valeur par défaut (non -i/interactive) est de sortir dans des répertoires séparés pour stdout/stderr, ce qu'elle fait par $ outputdir/$ hostname.

10
infowolfe

Si vous vous retrouvez à faire ce genre de tâche assez souvent, vous devriez essayer Fabric

Installez le Fabric en suivant les instructions , la plupart des cas dont vous avez juste besoin Sudo apt-get install fabric

Créez un fichier nommé fabfile.py avec le code suivant:

from fabric.api import env, run

env.hosts = ['server1.mydomain.com',
             'server1.mydomain.com',
             'server1.mydomain.com']

def mytask():
    run('uname -a')

Exécutez ensuite fab mytask vous donnera le résultat souhaité.

3
number5

Étant donné qu'une commande ssh prend tous les flux de l'entrée standard, alimentés par l'instruction while,

Vous pouvez utiliser un canal pour basculer le stdin de ssh vers une autre source:

echo "" | ssh ...

exemple :

while read Host ; do echo "" | ssh $Host "uname -a" ; done < servers.txt

l'entrée stdin de toutes les commandes ssh dans une boucle while doit être commutée vers une autre source.

2
Ali ISSA

Il est plus facile d'utiliser une commande comme celle-ci:

for f in `cat servers.txt`; do ssh $f uname -a; done

J'aime généralement ça:

for f in `cat servers.txt`; do echo "### $f ###"; ssh $f uname -a; done

Le echo permet de voir quel serveur est bloqué ou ne peut pas s'y connecter.

1
Ionut Ciucanu