GNU/Linux >> Tutoriels Linux >  >> Linux

Lecture de lignes à partir d'un fichier avec Bash :pour Vs. Tandis que?

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

J'ai donc une liste qui ressemble à ceci :

server1
server2
server3
server4

Je pensais pouvoir boucler cela 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 exécution, n'exécutant donc que uname -a sur le serveur1

Cependant, avec une boucle for utilisant cat, 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, c'est que 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 ?

Réponse acceptée :

Le for boucle est bien ici. Mais notez que cela est dû au fait que le fichier contient des noms de machine, qui ne contiennent aucun caractère d'espacement ou caractère global. for x in $(cat file); do … ne fonctionne pas pour parcourir les lignes de file en général, car le shell divise d'abord la sortie de la commande cat file partout où il y a des espaces, puis traite chaque mot comme un modèle glob donc \[?* sont encore élargis. Vous pouvez faire for x in $(cat file) sûr si vous y travaillez :

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

Lecture connexe :Boucler dans des fichiers avec des espaces dans les noms ? ; Comment puis-je lire ligne par ligne à partir d'une variable dans bash ?; Pourquoi while IFS= read utilisé si souvent, au lieu de IFS=; while read.. ? Notez que lors de l'utilisation de while read , la syntaxe sûre pour lire les lignes est while IFS= read -r line; do … .

Passons maintenant à ce qui ne va pas avec votre while read tenter. La redirection depuis le fichier de liste de serveurs s'applique à toute la boucle. Ainsi, lorsque ssh s'exécute, 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 envoie cette entrée au côté distant. Le serveur ssh est alors prêt à transmettre cette entrée à la commande distante, s'il le souhaite. Dans votre cas, la commande à distance ne lit jamais aucune entrée, donc les données finissent par être supprimées, mais le côté client n'en sait rien. Votre tentative avec echo a fonctionné parce que echo ne lit jamais aucune entrée, il laisse son entrée standard seule.

Il existe plusieurs façons d'éviter cela. Vous pouvez dire à ssh de ne pas lire à partir de l'entrée standard, avec le -n option.

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

Le -n l'option indique en fait ssh pour rediriger son entrée depuis /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 pour éviter que l'entrée de ssh ne provienne du fichier est de mettre la redirection sur le read commande :while read server </home/kenny/list_of_servers.txt; do … . Cela ne fonctionnera pas, car le fichier sera rouvert à chaque fois que le read La commande est exécutée (elle lit donc la première ligne du fichier encore et encore). La redirection doit être dans l'ensemble de la boucle while afin que le fichier soit ouvert une fois pendant toute la durée de la boucle.

En relation :BashScript fonctionne depuis Terminal mais pas depuis CronTab ?

La solution générale est de fournir l'entrée à la boucle sur un descripteur de fichier autre que l'entrée standard. Le shell a des constructions pour transporter l'entrée et la sortie d'un numéro de descripteur à un autre. Ici, nous ouvrons le fichier sur le descripteur de fichier 3, et redirigeons le read entré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

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

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


Linux
  1. 10 alias Bash pratiques pour Linux

  2. Fichier équivalent ".bashrc" lu par tous les shells ?

  3. Remplacer les lignes correspondant à un motif par des lignes d'un autre fichier dans l'ordre ?

  4. Comment remplir un fichier avec un flux de /dev/urandom avec un nombre de lignes spécifié ?

  5. Awk de différentes lignes ?

Comment lire des fichiers ligne par ligne dans Bash

Comment lire un fichier ligne par ligne dans Bash

Bash For Loop avec des exemples pratiques

La redirection bash expliquée avec des exemples

Script bash :comment lire des données à partir de fichiers texte

Bash Scripting Part2 - Boucles For et While avec exemples