Czytanie linii z pliku za pomocą bash: for vs. while

36

Próbuję odczytać plik tekstowy i zrobić coś z każdą linią, używając skryptu bash.

Mam więc listę, która wygląda następująco:

server1
server2
server3
server4

Pomyślałem, że mogę to zrobić za pomocą pętli while, tak:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

Pętla while zatrzymuje się po 1 uruchomieniu, dlatego działa tylko uname -ana serwerze server1

Jednak z pętlą for za pomocą cat działa dobrze:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

Jeszcze bardziej zaskakujące jest dla mnie to, że to działa:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

Dlaczego mój pierwszy przykład kończy się po pierwszej iteracji?

Kenny Rasschaert
źródło

Odpowiedzi:

25

forPętli jest tutaj dobrze. Należy jednak pamiętać, że dzieje się tak, ponieważ plik zawiera nazwy maszyn, które nie zawierają żadnych białych znaków ani znaków globowania. generalnie for x in $(cat file); do …nie działa iteracja po liniach file, ponieważ powłoka najpierw rozdziela dane wyjściowe od polecenia w cat filedowolnym miejscu spacji, a następnie traktuje każde słowo jako wzorzec globalny, więc \[?*są one dalej rozwijane. Możesz for x in $(cat file)zabezpieczyć się, jeśli nad tym pracujesz:

set -f
IFS='
'
for x in $(cat file); do 

Czytanie pokrewne: Pętli w plikach ze spacjami w nazwach? ; Jak mogę odczytać wiersz po wierszu ze zmiennej w bash? ; Dlaczego jest while IFS= readużywany tak często, a nie IFS=; while read..? Zauważ, że podczas używania while readbezpieczna składnia do odczytu linii to while IFS= read -r line; do ….

Teraz przejdźmy do tego, co idzie nie tak z Twoją while readpróbą. Przekierowanie z pliku listy serwerów dotyczy całej pętli. Kiedy sshdziała, jego standardowe wejście pochodzi z tego pliku. Klient ssh nie może wiedzieć, kiedy zdalna aplikacja może chcieć czytać ze swojego standardowego wejścia. Gdy tylko klient ssh zauważy jakieś dane wejściowe, wysyła je na stronę zdalną. Serwer ssh jest wtedy gotowy do wprowadzenia tych danych wejściowych do polecenia zdalnego, jeśli tego chce. W twoim przypadku zdalne polecenie nigdy nie odczytuje żadnych danych wejściowych, więc dane zostają odrzucone, ale strona klienta nic o tym nie wie. Twoja próba echozadziałała, ponieważ echonigdy nie czyta żadnych danych wejściowych, pozostawia swoje standardowe dane w spokoju.

Jest kilka sposobów, aby tego uniknąć. Z -nopcją możesz powiedzieć ssh, aby nie czytał ze standardowego wejścia .

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

Ta -nopcja mówi ssho przekierowaniu danych wejściowych /dev/null. Możesz to zrobić na poziomie powłoki, i zadziała dla każdego polecenia.

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

Sposób kuszące, aby uniknąć wejścia ssh pochodzące z pliku jest umieścić przekierowanie na readpolecenia: while read server </home/kenny/list_of_servers.txt; do …. To nie zadziała, ponieważ powoduje, że plik jest otwierany ponownie za każdym razem, gdy readkomenda jest wykonywana (aby w kółko czytał pierwszy wiersz pliku). Przekierowanie musi odbywać się w całej pętli while, aby plik był otwierany jeden raz na czas trwania pętli.

Ogólnym rozwiązaniem jest zapewnienie wejścia do pętli w deskryptorze pliku innym niż wejście standardowe. Powłoka ma konstrukcje do przesyłania danych wejściowych i wyjściowych z jednego numeru deskryptora do drugiego. Tutaj otwieramy plik na deskryptorze pliku 3 i przekierowujemy readstandardowe wejście polecenia z deskryptora pliku 3. Klient ssh ignoruje otwarte niestandardowe deskryptory, więc wszystko jest w porządku.

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

W bash readpolecenie ma określoną opcję odczytu z innego deskryptora pliku, więc możesz pisać read -u3 server.

Literatura pokrewna: Deskryptory plików i skrypty powłoki ; Kiedy użyjesz dodatkowego deskryptora pliku?

Gilles „SO- przestań być zły”
źródło
5
Człowieku, ta wymiana stosów z pewnością różni się od błędu serwera. Ludzie tutaj naprawdę starają się nie tylko odpowiedzieć, ale także edukować. Wielkie dzięki.
Kenny Rasschaert
26

Powinieneś użyć while, niefor . Aby uniknąć przełykania poleceń przez standardowe wejście w takiej pętli, wystarczy po prostu użyć innego deskryptora pliku :

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

Aby uzyskać więcej informacji help [r]ead( naprawdę ) i inny artykuł wyjaśniający dlaczego .

10b0
źródło
1
Co ciekawe, nigdy wcześniej nie korzystałem z deskryptorów plików. Co robi -uflaga? Nie jestem pewien, na jakiej stronie man powinienem to sprawdzić.
Kenny Rasschaert
Polecenie read jest częścią bash powłoki, więc znajduje się na stronie man bash w sekcji „SHELL BUILTIN COMMANDS” w przeczytanym akapicie. Znajdziesz „-u fd Odczytaj dane wejściowe z deskryptora pliku fd”. w tym akapicie
f4m8,
6
Alternatywnie help read- helpto polecenie pobierania odpowiednika manstron dla wbudowanych powłok.
l0b0
22

W twoim pierwszym kodzie ssh„wykradnie” STDIN z while. Dodaj -nopcję, aby sshtego uniknąć. Od man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
człowiek w pracy
źródło
Ok, masz pojęcie, dlaczego tak się nie dzieje z pętlą for?
Kenny Rasschaert
7
Ponieważ forpętla odbiera dane jako parametr, a nie jako dane wejściowe.
manatwork
1
Proszę pana, jesteś dżentelmenem i uczonym.
Kenny Rasschaert,
0

Domyślna standardowa obsługa wejścia sshopróżnia pozostałą linię z pętli while.

Aby uniknąć tego problemu, zmień, skąd problematyczne polecenie odczytuje standardowe dane wejściowe. Jeśli do komendy nie trzeba przekazywać żadnych standardowych danych wejściowych, należy odczytać standardowe dane wejściowe ze specjalnego /dev/nullurządzenia.

nixguru
źródło