Usando while loop para ssh a varios serveres

Tengo un file servers.txt , con una list de serveres:

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

cuando leo el file línea por línea con while y echo cada línea, todo funciona como se espera. Todas las líneas están impresas.

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

Sin embargo, cuando quiero enviar ssh a todos los serveres y ejecutar un command, de repente mi ciclo while deja de funcionar:

 $ 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 

Esto solo se conecta al primer server de la list, no a todos. No entiendo lo que está pasando aquí. ¿Alguien puede explicar?

Esto es aún más extraño, ya que usar for loop funciona 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 

Debe ser algo específico de ssh , porque otros commands funcionan bien, como ping :

 $ while read HOST ; do ping -c 1 $HOST ; done < servers.txt 

ssh está leyendo el rest de su input estándar.

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

read lee de stdin. El < networkingirige stdin desde un file.

Desafortunadamente, el command que está tratando de ejecutar también lee stdin, por lo que termina consumiendo el rest de su file. Puedes verlo claramente con:

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

Observe cómo el cat comió (y repitió) las dos líneas restantes. (Si lo hubiera leído como se esperaba, cada línea tendría el "inicio" y el "final" alnetworkingedor del host).

¿Por qué funciona for trabajo?

Su línea no networkingirige a stdin. (De hecho, lee todo el contenido del file servers.txt en la memory antes de la primera iteración). Entonces, ssh continúa leyendo su stdin desde la terminal (o posiblemente nada, dependiendo de cómo se llame su script).

Solución

Al less en bash, puede haber read usar un descriptor de file diferente.

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

debería funcionar 10 es solo un número de file arbitrario que elegí. 0, 1 y 2 tienen significados definidos y, por lo general, la apertura de files comenzará desde el primer número disponible (por lo que 3 será el siguiente). 10 es, por lo tanto, lo suficientemente alto como para permanecer fuera del path, pero lo suficientemente bajo como para estar por debajo del límite en algunos proyectiles. Además, es un buen número networkingondo …

Solución alternativa 1: -n

Como señala McNisse en su respuesta , el cliente de OpenSSH tiene una opción -n que impedirá que lea stdin. Esto funciona bien en el caso particular de ssh , pero, por supuesto, otros commands pueden carecer de esto; las otras soluciones funcionan independientemente de qué command consum su stdin.

Solución alternativa 2: segunda networkingirección

Al parecer, (como en, lo probé, funciona en mi versión de Bash al less …) haz una segunda networkingirección, que se ve así:

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

Puede usar esto con cualquier command, pero será difícil si realmente desea que la input del terminal vaya al command.

Como derobert describe ssh lee tu stdin .

Para cambiar este comportamiento, puede agregar -n no ssh para evitar que lea stdin.

 ssh -n $HOST "uname -a" 

Probablemente estarías mejor usando pssh del proyecto paralelo-ssh .

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

-i significa interactivo. pssh también viene con un scp paralelo y rsync paralelo. Lo bueno es que se ejecuta de forma asíncrona y ejecutará tantos subprocesss como le pidas. El valor pnetworkingeterminado (no-i / interactivo) es enviar a directorys separados para stdout / stderr, que lo hace por $ outputdir / $ hostname.

Si te encuentras realizando este tipo de tareas con bastante frecuencia, deberías probar Fabric

Instale la tela siguiendo las instrucciones , la mayoría de los casos solo necesita sudo apt-get install fabric

Crea un file llamado fabfile.py con el siguiente código:

 from fabric.api import env, run env.hosts = ['server1.mydomain.com', 'server1.mydomain.com', 'server1.mydomain.com'] def mytask(): run('uname -a') 

Luego ejecute fab mytask le dará el resultado que desea.

Es más fácil usar un command como este:

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

Por lo general, me gusta esto:

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

El echo es ver qué server está atascado, o no se puede conectar a él.

Porque una comunicación ssh toma todas las transmisiones de la input estándar, alimentadas por la instrucción while,

Puede usar un conducto para cambiar el stdin de ssh a otra fuente:

 echo "" | ssh ... 

ejemplo:

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

La input stdin de todos los commands ssh en un ciclo while debe cambiarse a otra fuente.