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.
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 ?