Używanie pętli while do ssh do wielu serwerów


75

Mam plik servers.txtz listą serwerów:

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

kiedy czytam plik linia po linii whilei echo każdej linii, wszystko działa zgodnie z oczekiwaniami. Wszystkie linie są drukowane.

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

Jednak gdy chcę ssh do wszystkich serwerów i wykonać polecenie, nagle moja whilepętla przestaje działać:

$ 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

To łączy się tylko z pierwszym serwerem na liście, a nie z wszystkimi. Nie rozumiem, co się tutaj dzieje. Czy ktoś może wyjaśnić?

Jest to nawet dziwniejsze, ponieważ użycie forpętli działa dobrze:

$ 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

Musi to być coś konkretnego ssh, ponieważ inne polecenia działają dobrze, takie jak ping:

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

4
użycieansible
Matt

Odpowiedzi:


98

ssh czyta resztę twojego standardowego wejścia.

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

readczyta ze standardowego. Do <przekierowania stdin z pliku.

Niestety polecenie, które próbujesz uruchomić, również odczytuje stdin, więc zjada resztę pliku. Możesz to wyraźnie zobaczyć za pomocą:

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

Zauważ, jak catzjadłem (i echo) pozostałe dwie linie. (Gdyby przeczytać to zgodnie z oczekiwaniami, każda linia miałaby „początek” i „koniec” wokół hosta).

Dlaczego fordziała

Twoja forlinia nie przekierowuje do standardowego wejścia. (W rzeczywistości odczytuje całą zawartość servers.txtpliku do pamięci przed pierwszą iteracją). Więc sshnadal odczytuje stdin z terminala (lub ewentualnie nic, w zależności od tego, jak wywoływany jest twój skrypt).

Rozwiązanie

Przynajmniej w bash możesz readużyć innego deskryptora pliku.

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

powinien działać. 10to tylko wybrany przeze mnie numer pliku. 0, 1 i 2 mają zdefiniowane znaczenie, a zazwyczaj otwieranie plików rozpocznie się od pierwszego dostępnego numeru (więc 3 będzie dalej używane). 10 jest więc wystarczająco wysoka, aby trzymać się z daleka, ale wystarczająco niska, aby być poniżej limitu w niektórych pociskach. Plus jest to ładny okrągły numer ...

Alternatywne rozwiązanie 1: -n

Jak wskazuje McNisse w swojej odpowiedzi , klient OpenSSH ma -nopcję, która uniemożliwi odczytanie standardowego wejścia. Działa to dobrze w konkretnym przypadku ssh, ale oczywiście innym poleceniom może tego brakować - inne rozwiązania działają niezależnie od tego, które polecenie zjada twoje standardowe wejście.

Alternatywne rozwiązanie 2: drugie przekierowanie

Możesz najwyraźniej (jak w, próbowałem, działa przynajmniej w mojej wersji Basha ...) zrobić drugie przekierowanie, które wygląda mniej więcej tak:

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

Możesz użyć tego z dowolną komendą, ale będzie to trudne, jeśli naprawdę chcesz, aby wejście terminala trafiło do komendy.


1
skąd 10pochodzi ten numer ?
Martin Vegter

2
@MartinVegter wymyśliłem. 0/1/2 to stdin, stdout i stderr. Możesz wybrać dowolną liczbę - bash pozwala ci pójść dość wysoko, prawdopodobnie nawet do limitu systemu operacyjnego. Inne pociski mogą ograniczyć cię do mniejszej liczby ...
derobert

@MartinVegter Zredagowałem, aby odpowiedzieć na oba twoje komentarze.
derobert

Inna alternatywa: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- powoduje, że deskryptor pliku 3 tworzy kopię zapasową oryginalnego stdin, następnie używa go do stdin ssh, a po zakończeniu zamyka FD kopii zapasowej. Działa to na dowolnej powłoce kompatybilnej z POSIX.
Richard Hansen

8
-uOpcja nie jest obsługiwana przez POSIX, a zatem nie powinny być wykorzystywane do #!/bin/shskryptów; użyj read HOST <&10zamiast tego. Ponadto POSIX wymaga tylko powłok do obsługi deskryptorów plików od 0 do 9, więc 10<servers.txtnie można go użyć, jeśli skrypt ma być ściśle zgodny.
Richard Hansen

28

Jak opisuje derobert sshczyta twoje stdin.

Aby zmienić to zachowanie, możesz dodać -n no ssh, aby uniemożliwić odczytanie standardowego wejścia.

ssh -n $HOST "uname -a"

10

Prawdopodobnie lepiej byłoby użyć pssh z projektu równoległego ssh .

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

-ioznacza interaktywny. pssh jest również wyposażony w równoległy scp i równoległy rsync. Zaletą jest to, że działa asynchronicznie i uruchamia tyle wątków, ile chcesz. Domyślnie (non -i / interactive) jest wysyłany do osobnych katalogów dla stdout / stderr, co robi przez $ outputdir / $ hostname.


3

Jeśli często wykonujesz tego rodzaju zadania, powinieneś wypróbować Fabric

Zainstaluj sieć zgodnie z instrukcjami , w większości przypadków, których potrzebujeszsudo apt-get install fabric

Utwórz plik o nazwie fabfile.pyz następującym kodem:

from fabric.api import env, run

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

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

Następnie bieg fab mytaskda wynik, który chcesz.


1

Łatwiej jest użyć takiego polecenia:

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

Zwykle lubię to:

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

Chodzi o echoto, który serwer utknął lub nie może się z nim połączyć.


1

Ponieważ komenda ssh pobiera cały strumień ze standardowego wejścia, zasilanego przez instrukcję while,

Możesz użyć potoku, aby przełączyć standardowe wejście ssh na inne źródło:

echo "" | ssh ...

przykład:

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

wejście stdin wszystkich poleceń ssh w pętli while musi zostać przełączone na inne źródło.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.