web-dev-qa-db-fra.com

Lignes de lecture d'un fichier avec Bash: pour vs. tout en

J'essaie de lire un fichier texte et de faire quelque chose avec chaque ligne, à l'aide d'un script Bash.

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

server1
server2
server3
server4

Je pensais que je pouvais boucler sur ceci en utilisant une boucle tandis que:

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

La boucle tandis que la boucle s'arrête après 1 exécution, ne fonctionnant que uname -a sur serveur1

Cependant, avec une boucle pour la chat, cela fonctionne bien:

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

Encore plus de dérangement pour moi, c'est que cela fonctionne également:

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?

36
Kenny Rasschaert

La boucle for va bien ici. Mais notez que cela est dû au fait que le fichier contient des noms de machine, qui ne contiennent aucun caractères blancheurs ni caractères de globe. for x in $(cat file); do … ne fonctionne pas à itérer sur les lignes de file en général, car la coque scindre d'abord la sortie de la commande cat file n'importe où il y a des espaces blancheurs, puis traite chaque mot comme un motif global Alors \[?* sont plus élargis. Vous pouvez faire for x in $(cat file) sûr si vous travaillez dessus:

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

Lecture associée: boucle via des fichiers avec des espaces dans les noms ? ; Comment puis-je lire la ligne par ligne d'une variable de bash ? ; Pourquoi while IFS= read est-il utilisé si souvent, au lieu de IFS=; while read.. ? Notez que lorsque vous utilisez while read, la syntaxe sécurisée pour lire les lignes est while IFS= read -r line; do ….

Maintenant, tournons à ce qui ne va pas avec votre while read tentative. La redirection du fichier de liste de serveurs s'applique à la boucle entière. Donc, quand ssh fonctionne, son entrée standard provient de ce fichier. Le client SSH ne peut pas savoir lorsque la demande distante voudra peut-être lire à partir de son entrée standard. Donc, dès que le client SSH remarque une entrée, il envoie cette entrée à la télécommande. Le serveur SSH est donc prêt à alimenter cette entrée sur la commande à distance, le cas échéant. Dans votre cas, la commande distante ne lit jamais une entrée, de sorte que les données se terminent rejetées, mais le côté client ne sait rien à ce sujet. Votre tentative avec echo travaillé parce que echo ne lit jamais aucune entrée, elle laisse son entrée standard seule.

Il y a quelques façons que vous pouvez éviter cela. Vous pouvez dire à SSH de ne pas lire à partir de l'entrée standard, avec l'option -n.

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

L'option -n en fait indique que ssh _ Pour rediriger son entrée de /dev/null . Vous pouvez le faire au niveau de la coquille 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 pour éviter les entrées de SSH provenant du fichier consiste à placer la redirection sur la commande read: while read server </home/kenny/list_of_servers.txt; do …. Cela ne fonctionnera pas, car il provoque l'ouverture du fichier à nouveau à chaque fois que la commande read est exécutée (il s'agirait de la première ligne du fichier sur et plus). La redirection doit être dans l'ensemble tandis que la boucle de sorte que le fichier soit ouvert une fois pendant 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. La coquille a des constructions pour ferry entré et sortie d'un numéro de descripteur à un autre. Ici, nous ouvrons le fichier sur le descripteur de fichier 3 et rediriger l'entrée standard read de la commande du descripteur de fichier 3. Le client SSH ignore les descripteurs ouverts non standard, de sorte que tout va bien.

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

En Bash, la commande read a une option spécifique à lire à partir d'un descripteur de fichier différent, vous pouvez donc écrire read -u3 server.

Lecture associée: Descripteurs de fichier et script shell ; Quand utiliseriez-vous un descripteur de fichier supplémentaire ?

Vous devez utiliser while, pas for . Le moyen d'éviter les commandes avaler une entrée standard dans une telle boucle est simplement d'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 n autre article expliquant pourquoi .

26
l0b0

La manipulation des entrées standard par défaut de ssh draine la ligne restante de la boucle tandis que.

Pour éviter ce problème, modifiez-vous lorsque la commande problématique lit l'entrée standard de. Si aucune entrée standard n'a besoin d'être transmise à la commande, lisez l'entrée standard de la spéciale /dev/null dispositif.

0
nixguru