desenv-web-rp.com

Usando while loop para ssh em vários servidores

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 while e eco 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 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 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 usar o loop for 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
82
Martin Vegter

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

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

read lê 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 cat comeu (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 for funciona?

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

Solução

Pelo menos no bash, você pode read usar 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 3 a ser usado). 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 número redondo agradável ...

Solução alternativa 1: -n

Como McNisse aponta em sua resposta , o cliente OpenSSH tem um -n opção que impedirá a leitura de stdin. Isso funciona bem no caso específico 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 em, 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.

102
derobert

Como derobert descreve ssh lê seu stdin.

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

ssh -n $Host "uname -a"
33
McNisse

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

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

-i significa interativo. O pssh também vem com um scp e um rsync paralelos. O que é legal é 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.

10
infowolfe

Se você se encontra 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')

Então corra fab mytask lhe dará o resultado desejado.

3
number5

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.

2
Ali ISSA

É 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á travado ou não pode se conectar a ele.

1
Ionut Ciucanu