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
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é).
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 ...
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.
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.
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"
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.
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é.
É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.
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.