Utilizzo di while loop per ssh su più server


75

Ho un file servers.txt, con un elenco di server:

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

quando leggo il file riga per riga whileed echo ogni riga, tutto funziona come previsto. Tutte le linee sono stampate.

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

Tuttavia, quando voglio ssh su tutti i server ed eseguire un comando, improvvisamente il mio whileciclo smette di funzionare:

$ 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

Questo si collega solo al primo server nell'elenco, non a tutti. Non capisco cosa sta succedendo qui. Qualcuno può spiegare per favore?

Questo è ancora più strano, poiché l'utilizzo di forloop funziona bene:

$ 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

Deve essere qualcosa di specifico ssh, perché altri comandi funzionano bene, come ping:

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

4
usoansible
Matt

Risposte:


98

ssh sta leggendo il resto del tuo input standard.

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

readlegge da stdin. I <redirect STDIN da un file.

Sfortunatamente, il comando che stai tentando di eseguire legge anche stdin, quindi finisce per mangiare il resto del tuo file. Puoi vederlo chiaramente con:

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

Notare come hanno catmangiato (e fatto eco) le restanti due righe. (Avevo letto fatto come previsto, ogni riga avrebbe "inizio" e "fine" intorno all'host.)

Perché forfunziona

La tua forlinea non reindirizza a stdin. (In effetti, legge l'intero contenuto del servers.txtfile in memoria prima della prima iterazione). Quindi sshcontinua a leggere il suo stdin dal terminale (o forse nulla, a seconda di come viene chiamato il tuo script).

Soluzione

Almeno in bash, puoi avere readun descrittore di file diverso.

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

dovrebbe funzionare. 10è solo un numero di file arbitrario che ho scelto. 0, 1 e 2 hanno significati definiti e, in genere, l'apertura dei file inizierà dal primo numero disponibile (quindi 3 sarà il prossimo da utilizzare). 10 è quindi abbastanza alto da stare lontano dalla strada, ma abbastanza basso da essere sotto il limite in alcuni gusci. Inoltre è un bel numero tondo ...

Soluzione alternativa 1: -n

Come sottolinea McNisse nella sua risposta , il client OpenSSH ha -nun'opzione che gli impedirà di leggere stdin. Questo funziona bene nel caso particolare di ssh, ma ovviamente altri comandi potrebbero non avere questo: le altre soluzioni funzionano indipendentemente da quale comando stia mangiando il tuo stdin.

Soluzione alternativa 2: secondo reindirizzamento

Apparentemente puoi (come in, l'ho provato, funziona almeno nella mia versione di Bash ...) fare un secondo reindirizzamento, che assomiglia a questo:

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

Puoi usarlo con qualsiasi comando, ma sarà difficile se in realtà vuoi che l'input del terminale vada al comando.


1
da dove viene il numero 10?
Martin Vegter,

2
@MartinVegter L'ho inventato. 0/1/2 sono stdin, stdout e stderr. Puoi scegliere qualsiasi numero che ti piace: bash ti lascia andare piuttosto in alto, probabilmente fino al limite del sistema operativo. Altre conchiglie potrebbero limitarti a meno ...
derobert

@MartinVegter Ho modificato per rispondere a entrambi i tuoi commenti.
derobert

Un'altra alternativa: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- questo rende il descrittore di file 3 un backup dello stdin originale, quindi lo utilizza per lo stdin di ssh, quindi al termine chiude l'FD di backup. Funziona su qualsiasi shell compatibile con POSIX.
Richard Hansen,

8
L' -uopzione non è supportata da POSIX e quindi non dovrebbe essere usata per gli #!/bin/shscript; usa read HOST <&10invece. Inoltre, POSIX richiede solo shell per supportare i descrittori di file da 0 a 9, quindi 10<servers.txtnon può essere utilizzato se lo script deve essere rigorosamente conforme.
Richard Hansen,

28

Come descrive Derobert il sshtuo stdin.

Per modificare questo comportamento è possibile aggiungere -n no ssh per impedire che legga stdin.

ssh -n $HOST "uname -a"

10

Probabilmente sarebbe meglio usare pssh dal progetto parallel-ssh .

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

-isignifica interattivo. pssh viene fornito con uno scp parallelo e rsync parallelo. La cosa bella è che viene eseguito in modo asincrono e eseguirà tutti i thread richiesti. L'impostazione predefinita (non -i / interattivo) è l'output in directory separate per stdout / stderr, cosa che fa per $ outputdir / $ hostname.


3

Se ti ritrovi a svolgere questo tipo di attività abbastanza spesso, dovresti provare Fabric

Installa il Fabric seguendo le istruzioni , la maggior parte dei casi di cui hai solo bisognosudo apt-get install fabric

Creare un file denominato fabfile.pycon il seguente codice:

from fabric.api import env, run

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

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

Quindi esegui fab mytaskti darà il risultato che desideri.


1

È più facile usare un comando come questo:

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

Di solito mi piace così:

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

Il echoè quello di vedere quale server è bloccato, o non può connettersi ad esso.


1

Poiché un comando ssh prende tutto il flusso dall'input standard, alimentato dall'istruzione while,

Puoi usare una pipe per cambiare lo stdin di ssh su un'altra fonte:

echo "" | ssh ...

esempio :

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

l'input stdin di tutti i comandi ssh in un ciclo while deve essere commutato su un'altra sorgente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.