Lire des lignes d’un fichier avec bash: pour vs tout le temps

36

J'essaie de lire un fichier texte et de faire quelque chose avec chaque ligne, en utilisant un script bash.

Donc, j'ai une liste qui ressemble à ceci:

server1
server2
server3
server4

Je pensais pouvoir boucler cette boucle en utilisant une boucle while, comme ceci:

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

La boucle while s'arrête après 1 course, donc seulement uname -a sur le serveur1

Cependant, avec une boucle for utilisant chat, cela fonctionne bien:

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

Encore plus déconcertant pour moi, cela fonctionne aussi:

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

Pourquoi mon premier exemple s'arrête-t-il après la première itération?

Kenny Rasschaert
la source

Réponses:

25

La forboucle est bien ici. Notez cependant que cela est dû au fait que le fichier contient des noms de machine, qui ne contiennent aucun caractère d’espace ni de caractère de substitution. for x in $(cat file); do …ne fonctionne pas pour parcourir les lignes de fileen général, car le shell divise d'abord la sortie de la commande cat filepartout où il y a des espaces, puis traite chaque mot comme un motif global afin qu'il \[?*soit développé davantage. Vous pouvez sécuriser for x in $(cat file)si vous travaillez dessus:

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

Lecture connexe: Parcourir les fichiers avec des espaces dans les noms? ; Comment puis-je lire ligne par ligne à partir d'une variable dans bash? ; Pourquoi est while IFS= readutilisé si souvent, au lieu de IFS=; while read..? Notez qu'en cas d'utilisation while read, la syntaxe sécurisée pour lire les lignes est la suivante:while IFS= read -r line; do … .

Voyons maintenant ce qui ne va pas avec votre while readtentative. La redirection à partir du fichier de liste de serveurs s’applique à l’ensemble de la boucle. Ainsi, lors de son sshexécution, son entrée standard provient de ce fichier. Le client ssh ne peut pas savoir quand l'application distante peut vouloir lire à partir de son entrée standard. Ainsi, dès que le client ssh remarque une entrée, il l'envoie au côté distant. Le serveur ssh est alors prêt à alimenter cette entrée avec la commande à distance, s’il le souhaite. Dans votre cas, la commande à distance ne lit jamais d’entrée, les données sont donc ignorées, mais le côté client ne sait rien à ce sujet. Votre tentative avec echowork car echone lit jamais d’entrée, elle laisse son entrée standard toute seule.

Vous pouvez éviter cela de plusieurs manières. Vous pouvez dire à ssh de ne pas lire depuis l'entrée standard, avec l' -noption.

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

L' -noption indique en fait sshde rediriger son entrée /dev/null. Vous pouvez le faire au niveau du shell et cela fonctionnera pour n'importe quelle commande.

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

Une méthode tentante à l'entrée de ssh éviter d' entrer dans le fichier est de mettre la redirection sur la readcommande: while read server </home/kenny/list_of_servers.txt; do …. Cela ne fonctionnera pas, car le fichier sera ouvert à chaque fois que la readcommande est exécutée (la première ligne du fichier sera lue encore et encore). La redirection doit être globalement en boucle while pour que le fichier ne soit ouvert qu'une fois pour la durée de la boucle.

La solution générale consiste à fournir l’entrée à la boucle sur un descripteur de fichier autre que l’entrée standard. L’enveloppe dispose de structures pour acheminer les entrées et les sorties d’un numéro de descripteur à un autre. Ici, nous ouvrons le fichier sur le descripteur de fichier 3 et redirigeons l' readentrée standard de la commande à partir du descripteur de fichier 3. Le client ssh ignore les descripteurs non standard ouverts, donc tout va bien.

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

Sous bash, la readcommande dispose d'une option spécifique permettant de lire à partir d'un descripteur de fichier différent, afin que vous puissiez écrire read -u3 server.

Lecture connexe: Descripteurs de fichiers et scripts shell ; Quand utiliseriez-vous un descripteur de fichier supplémentaire?

Gilles, arrête de faire le mal
la source
5
Man, ce stackexchange est différent de serverfault. Les gens ici s’efforcent vraiment non seulement de répondre, mais aussi d’éduquer. Merci beaucoup.
Kenny Rasschaert
26

Vous devriez utiliser while, pasfor . Le moyen d'éviter que les commandes avalent l'entrée standard dans une telle boucle consiste simplement à utiliser un autre descripteur de fichier :

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

Pour plus d'informations, help [r]ead( vraiment ) et un autre article expliquant pourquoi .

l0b0
la source
1
Intéressant, je n'ai jamais utilisé de descripteur de fichier auparavant. Que fait le -udrapeau? Je ne suis pas sûr de savoir dans quelle page de manuel je suis censé l'examiner.
Kenny Rasschaert
La commande read fait partie du bash du shell, elle se trouve donc dans la page de manuel de bash de la section "SHELL BUILTIN COMMANDS" du paragraphe read. Vous trouverez "-u fd Lire les entrées du descripteur de fichier fd". dans ce paragraphe
f4m8
6
Alternativement help read- helpest la commande pour obtenir l'équivalent de manpages pour les commandes intégrées du shell.
l0b0
22

Dans votre premier code sshva "voler" le STDIN de la while. Ajoutez l' -noption pour l' sshéviter. De man ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).
homme au travail
la source
Ok, vous savez pourquoi cela n'arrive pas avec la boucle for?
Kenny Rasschaert
7
Parce que la forboucle reçoit les données en paramètre, pas en entrée.
Manatwork
1
Vous, Monsieur, êtes un gentleman et un savant.
Kenny Rasschaert
0

Le traitement d’entrée standard par défaut de ssh draine la ligne restante de la boucle while.

Pour éviter ce problème, modifiez l'emplacement où la commande problématique lit l'entrée standard. Si aucune entrée standard ne doit être transmise à la commande, lisez l'entrée standard du /dev/nullpériphérique spécial .

nixguru
la source