Usando while loop para ssh em vários servidores

75

Eu tenho um arquivo servers.txt, com lista de servidores:

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

quando leio o arquivo linha por linha com whilee ecoo cada linha, tudo funciona conforme o esperado. Todas as linhas são impressas.

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

No entanto, quando eu quero ssh para todos os servidores e executar um comando, de repente meu whileloop para 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

Isso se conecta apenas ao primeiro servidor da lista, não a todos eles. Eu não entendo o que está acontecendo aqui. Alguém pode me explicar?

Isso é ainda mais estranho, pois o uso de forloop funciona bem:

$ 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 ser algo específico ssh, porque outros comandos funcionam bem, como ping:

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt
Martin Vegter
fonte
4
usoansible
Matt

Respostas:

98

ssh está lendo o restante de sua entrada padrão.

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

readlê de stdin. O <redireciona stdin de um arquivo.

Infelizmente, o comando que você está tentando executar também lê stdin, e acaba consumindo o restante do seu arquivo. Você pode ver claramente com:

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

Observe como catcomeu (e ecoou) as duas linhas restantes. (Se o tivesse lido conforme o esperado, cada linha teria o "início" e o "fim" em torno do host.)

Por que forfunciona?

Sua forlinha não redireciona para stdin. (De fato, ele lê todo o conteúdo do servers.txtarquivo na memória antes da primeira iteração). Então, sshcontinua a ler seu stdin no terminal (ou possivelmente nada, dependendo de como seu script é chamado).

Solução

Pelo menos no bash, você pode readusar um descritor de arquivo diferente.

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

deveria funcionar. 10é apenas um número de arquivo arbitrário que eu escolhi. 0, 1 e 2 definiram significados e, normalmente, a abertura de arquivos será iniciada a partir do primeiro número disponível (então será o próximo a ser usado 3). 10 é alto o suficiente para ficar fora do caminho, mas baixo o suficiente para ficar abaixo do limite em algumas conchas. Além disso, é um bom número redondo ...

Solução alternativa 1: -n

Como McNisse aponta em sua resposta , o cliente OpenSSH tem uma -nopção que impede que ele leia stdin. Isso funciona bem no caso particular de ssh, mas é claro que outros comandos podem não ter isso - as outras soluções funcionam independentemente de qual comando está consumindo seu stdin.

Solução alternativa 2: segundo redirecionamento

Você pode aparentemente (como eu tentei, funciona na minha versão do Bash pelo menos ...) fazer um segundo redirecionamento, que se parece com isso:

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

Você pode usar isso com qualquer comando, mas será difícil se você realmente quiser que a entrada do terminal vá para o comando.

derobert
fonte
1
de onde vem o número 10?
Martin Vegter
2
@MartinVegter eu inventei. 0/1/2 são stdin, stdout e stderr. Você pode escolher qualquer número que desejar - o bash permite aumentar bastante, provavelmente até o limite do sistema operacional. Outros conchas pode limitá-lo para menos ...
derobert
@MartinVegter Eu editei para responder a ambos os seus comentários.
derobert
Outra alternativa: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- isso torna o descritor de arquivo 3 um backup do stdin original, depois o usa para o stdin do ssh e, em seguida, fecha o FD do backup quando concluído. Isso funciona em qualquer shell compatível com POSIX.
Richard Hansen
8
A -uopção não é suportada pelo POSIX e, portanto, não deve ser usada para #!/bin/shscripts; use em read HOST <&10vez disso. Além disso, o POSIX requer apenas shells para dar suporte aos descritores de arquivos de 0 a 9, portanto, 10<servers.txtnão pode ser usado se o script estiver em conformidade estrita.
Richard Hansen
28

Como derobert descreve sshlê o seu stdin.

Para alterar esse comportamento, você pode adicionar -n no ssh para impedir que ele leia stdin.

ssh -n $HOST "uname -a"
McNisse
fonte
10

Você provavelmente seria melhor usar o pssh do projeto paralelo-ssh .

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

-isignifica interativo. O pssh também vem com um scp e um rsync paralelos. O interessante é que ele é executado de forma assíncrona e executa quantos threads você solicita. O padrão (não -i / interativo) é produzir em diretórios separados para stdout / stderr, o que é feito por $ outputdir / $ hostname.

infowolfe
fonte
3

Se você se encontra fazendo esse tipo de tarefa com bastante frequência, tente o Fabric

Instale o Fabric seguindo as instruções , na maioria dos casos, você só precisasudo apt-get install fabric

Crie um arquivo nomeado fabfile.pycom o seguinte código:

from fabric.api import env, run

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

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

Em seguida, execute fab mytasko resultado desejado.

número 5
fonte
1

É mais fácil usar um comando como este:

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

Eu costumo fazer assim:

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

O echoé para ver qual servidor está bloqueado ou não pode se conectar a ele.

Ionut Ciucanu
fonte
1

Como um commn ssh obtém todo o fluxo da entrada padrão, alimentada pela instrução while,

Você pode usar um pipe para alternar o stdin do ssh para outra fonte:

echo "" | ssh ...

exemplo:

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

A entrada stdin de todos os comandos ssh em um loop while deve ser alternada para outra fonte.

Ali ISSA
fonte