Utilizando while loop para ssh em vários servidores

67

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

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

quando leio o arquivo linha a linha com while e faço eco de cada linha, tudo funciona como 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 loop while 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 só se conecta 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, já que usar for loop 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 para ssh , porque outros comandos funcionam bem, como ping :

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt
    
por Martin Vegter 04.01.2014 / 08:47

6 respostas

86

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

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

read lê a partir de stdin. O < redireciona o stdin de um arquivo.

Infelizmente, o comando que você está tentando executar também lê stdin, então ele acaba comendo o resto do 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 cat comeu (e ecoou) as duas linhas restantes. (Tinha lido feito como esperado, cada linha teria o "início" e "fim" em torno do host.)

Por que for funciona?

Sua linha for não redireciona para stdin. (Na verdade, ele lê todo o conteúdo do arquivo servers.txt na memória antes da primeira iteração). Portanto, ssh continua a ler seu stdin do terminal (ou possivelmente nada, dependendo de como seu script é chamado).

Solução

Pelo menos no bash, você pode ter read usando um descritor de arquivo diferente.

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

deve funcionar. 10 é apenas um número de arquivo arbitrário que eu escolhi. 0, 1 e 2 têm significados definidos e, normalmente, os arquivos de abertura começarão a partir do primeiro número disponível (portanto, 3 estará ao lado). 10 é, portanto, alto o suficiente para ficar fora do caminho, mas baixo o suficiente para estar 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 opção -n que impedirá a leitura de 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á comendo sua stdin.

Solução alternativa 2: segundo redirecionamento

Você pode aparentemente (como em, eu tentei, funciona na minha versão do Bash pelo menos ...) fazer um segundo redirecionamento, que é algo como isto:

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.

    
por 04.01.2014 / 08:52
27

Como o derobert descreve ssh , você lê seu stdin .

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

ssh -n $HOST "uname -a"
    
por 04.01.2014 / 09:04
10

Provavelmente, seria melhor usar pssh do paralelo-ssh .

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

-i significa interativo. O pssh também vem com um scp paralelo e um rsync paralelo. O que é legal é que ele é executado de forma assíncrona e executará o maior número de threads que você solicitar. O padrão (não -i / interativo) é a saída para diretórios separados para stdout / stderr, o que é feito por $ outputdir / $ hostname.

    
por 04.01.2014 / 18:17
3

Se você estiver fazendo esse tipo de tarefa com bastante frequência, tente Fabric

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

Crie um arquivo chamado fabfile.py com 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 mytask para obter o resultado desejado.

    
por 07.01.2014 / 01:31
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á preso ou não pode se conectar a ele.

    
por 08.01.2014 / 06:57
0

Como um ssh se comunica com todo o fluxo da entrada padrão, alimentado pela instrução while,

Você pode usar um pipe para alternar o stdin de 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 comutada para outra fonte.

    
por 02.06.2016 / 01:35