Zeilen aus einer Datei lesen mit bash: für vs. while

36

Ich versuche, eine Textdatei zu lesen und mit jeder Zeile etwas zu tun, indem ich ein Bash-Skript verwende.

Also, ich habe eine Liste, die so aussieht:

server1
server2
server3
server4

Ich dachte, ich könnte dies mit einer while-Schleife durchlaufen, wie folgt:

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

Die while-Schleife stoppt nach 1 Ausführung und wird daher nur uname -aauf Server1 ausgeführt

Mit einer for-Schleife mit cat funktioniert dies jedoch einwandfrei:

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

Noch verblüffender ist für mich, dass dies auch funktioniert:

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

Warum stoppt mein erstes Beispiel nach der ersten Iteration?

Kenny Rasschaert
quelle

Antworten:

25

Die forSchleife ist hier in Ordnung. Beachten Sie jedoch, dass dies daran liegt, dass die Datei Computernamen enthält, die keine Whitespace- oder Globbing-Zeichen enthalten. for x in $(cat file); do …Das Iterieren über die Zeilen von funktioniert fileim Allgemeinen nicht, da die Shell die Ausgabe des Befehls zunächst an einer cat filebeliebigen Stelle mit Leerzeichen aufteilt und dann jedes Wort als Glob-Muster behandelt, um \[?*es weiter zu erweitern. Sie können auf Nummer for x in $(cat file)sicher gehen, wenn Sie daran arbeiten:

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

Weiterführende Literatur: Durchlaufen von Dateien mit Leerzeichen in den Namen? ; Wie kann ich Zeile für Zeile aus einer Variablen in Bash lesen? ; Warum wird while IFS= readso oft verwendet, anstatt IFS=; while read..? Beachten Sie, dass bei Verwendung while readdie sichere Syntax zum Lesen von Zeilen lautet while IFS= read -r line; do ….

Wenden wir uns nun dem zu, was bei Ihrem while readVersuch schief geht . Die Umleitung aus der Serverlistendatei gilt für die gesamte Schleife. Wenn also ausgeführt wird ssh, stammt die Standardeingabe aus dieser Datei. Der ssh-Client kann nicht wissen, wann die entfernte Anwendung von ihrer Standardeingabe lesen möchte. Sobald der ssh-Client eine Eingabe bemerkt, sendet er diese Eingabe an die entfernte Seite. Der dortige ssh-Server ist dann bereit, diese Eingabe an den Remote-Befehl weiterzuleiten, falls er dies wünscht. In Ihrem Fall liest der Remote-Befehl niemals Eingaben, sodass die Daten verworfen werden, aber die Clientseite weiß nichts darüber. Ihr Versuch mit hat echofunktioniert, weil er echokeine Eingaben liest, sondern seine Standardeingaben in Ruhe lässt.

Es gibt einige Möglichkeiten, wie Sie dies vermeiden können. Mit der -nOption können Sie ssh anweisen, nicht von der Standardeingabe zu lesen .

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

Die -nOption gibt an, dass sshdie Eingabe von umgeleitet werden soll /dev/null. Sie können das auf der Shell-Ebene tun, und es wird für jeden Befehl funktionieren.

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

Eine verlockende Methode zu vermeiden ssh die Eingabe aus der Datei kommt , ist die Umleitung auf den setzen readBefehl: while read server </home/kenny/list_of_servers.txt; do …. Dies funktioniert nicht, da die Datei bei jeder readAusführung des Befehls erneut geöffnet wird (sodass die erste Zeile der Datei immer wieder gelesen wird). Die Umleitung muss in der gesamten while-Schleife erfolgen, damit die Datei für die Dauer der Schleife einmal geöffnet wird.

Die allgemeine Lösung besteht darin, die Eingabe für die Schleife in einem anderen Dateideskriptor als der Standardeingabe bereitzustellen . Die Shell verfügt über Konstrukte, um die Eingabe und Ausgabe von einer Deskriptornummer zur nächsten zu übermitteln. Hier öffnen wir die Datei in Dateideskriptor 3 und leiten die readStandardeingabe des Befehls von Dateideskriptor 3 um. Der ssh-Client ignoriert offene Nicht-Standarddeskriptoren, sodass alles in Ordnung ist.

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

In bash hat der readBefehl eine bestimmte Option zum Lesen aus einem anderen Dateideskriptor, sodass Sie schreiben können read -u3 server.

Verwandte Lektüre: Dateideskriptoren & Shell-Scripting ; Wann würden Sie einen zusätzlichen Dateideskriptor verwenden?

Gilles 'SO - hör auf böse zu sein'
quelle
5
Mann, dieser Stackexchange unterscheidet sich sicher vom Serverfault. Die Leute hier tun wirklich alles, um nicht nur zu antworten, sondern auch zu erziehen. Vielen Dank.
Kenny Rasschaert
26

Sie sollten whilenicht verwendenfor . Um zu vermeiden, dass Befehle die Standardeingabe in einer solchen Schleife verschlucken, verwenden Sie einfach einen anderen Dateideskriptor :

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

Für weitere Informationen help [r]ead( wirklich ) und einen weiteren Artikel, in dem erklärt wird, warum .

l0b0
quelle
1
Interessant, ich habe noch nie zuvor Dateideskriptoren verwendet. Was macht die -uFlagge? Ich bin nicht sicher, auf welcher Manpage ich es nachschlagen soll.
Kenny Rasschaert
Der Lesebefehl ist Teil der Shell-Bash, daher finden Sie ihn in der Manpage von Bash im Abschnitt "SHELL-BUILTIN-BEFEHLE" im gelesenen Absatz. Sie finden "-u fd Liest die Eingabe aus dem Dateideskriptor fd." in diesem Absatz
f4m8
6
Alternativ help read- helpist der Befehl zum manAbrufen der Entsprechung von Seiten für Shell-Buildins.
10.
22

In Ihrem ersten Code sshwird die STDIN von der "stehlen" while. -nOption hinzufügen ssh, um dies zu vermeiden. Von man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
Mann bei der Arbeit
quelle
Ok, hast du eine Idee, warum das mit der for-Schleife nicht passiert?
Kenny Rasschaert
7
Weil die forSchleife die Daten als Parameter empfängt, nicht als Eingabe.
Manatwork
1
Sie, mein Herr, sind ein Gentleman und ein Gelehrter.
Kenny Rasschaert
0

Bei der Standardeingabebehandlung von wird sshdie verbleibende Zeile aus der while-Schleife entfernt.

Ändern Sie, um dieses Problem zu vermeiden, woher der problematische Befehl die Standardeingabe liest. Wenn keine Standardeingabe an den Befehl übergeben werden muss, lesen Sie die Standardeingabe vom Spezialgerät /dev/null.

Nixguru
quelle