185 votes

La boucle While arrête la lecture après la première ligne en Bash

J'ai le shell script suivant. Le but est de boucler à travers chaque ligne du fichier cible (dont le chemin est le paramètre d'entrée du script) et de faire un travail contre chaque ligne. Maintenant, il semble ne fonctionner qu'avec la toute première ligne du fichier cible et s'arrête après que cette ligne ait été traitée. Y a-t-il un problème avec mon script ?

#!/bin/bash
# SCRIPT: do.sh
# PURPOSE: loop thru the targets 

FILENAME=$1
count=0

echo "proceed with $FILENAME"

while read LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh $LINE
done < $FILENAME

echo "\ntotal $count targets"

En do_work.sh Je dirige un couple de ssh des commandes.

283voto

dogbane Points 85749

Le problème est que do_work.sh fonctionne ssh et par défaut ssh lit depuis stdin qui est votre fichier d'entrée. Par conséquent, vous ne voyez que la première ligne traitée, car la commande consomme le reste du fichier et votre boucle while se termine.

Cela ne se produit pas seulement pour ssh mais pour toute commande qui lit stdin, y compris mplayer , ffmpeg , HandBrakeCLI , httpie , brew install et plus encore.

Pour éviter cela, passez l'option -n à votre ssh pour qu'il lise à partir de /dev/null au lieu de stdin. D'autres commandes ont des drapeaux similaires, ou vous pouvez universellement utiliser < /dev/null .

66voto

M.P. Points 725

Une solution de contournement très simple et robuste consiste à modifier l'adresse de l'utilisateur. descripteur de fichier à partir duquel le read reçoit une entrée.

Ceci est accompli par deux modifications : le -u argument pour read et l'opérateur de redirection pour < $FILENAME .

En BASH, les valeurs par défaut des descripteurs de fichiers (c'est-à-dire les valeurs de -u en read ) sont :

  • 0 = stdin
  • 1 = stdout
  • 2 = stderr

Il suffit donc de choisir un autre descripteur de fichier inutilisé, par exemple 9 juste pour le plaisir.

Ainsi, la solution suivante serait la solution de rechange :

while read -u 9 LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh $LINE
done 9< $FILENAME

Remarquez les deux modifications :

  1. read devient read -u 9
  2. < $FILENAME devient 9< $FILENAME

En tant que meilleure pratique, je fais cela pour toute while boucles que j'écris en BASH. Si vous avez des boucles imbriquées utilisant read utiliser un autre descripteur de fichier pour chacun d'eux (9,8,7,...).

36voto

tripleee Points 28746

De manière plus générale, une solution de contournement qui n'est pas spécifique à la ssh est de rediriger l'entrée standard pour toute commande qui, autrement, pourrait consommer l'option while l'entrée de la boucle.

while read -r LINE; do
   let count++
   echo "$count $LINE"
   sh ./do_work.sh "$LINE" </dev/null
done < "$FILENAME"

L'ajout de </dev/null est le point crucial ici (bien que la citation corrigée soit aussi quelque peu importante ; voir aussi Quand mettre des guillemets autour d'une variable shell ? ). Vous voudrez utiliser read -r à moins que vous n'ayez spécifiquement besoin de l'héritage du comportement légèrement étrange que vous obtenez sans -r .

Une autre solution de contournement qui est quelque peu spécifique à ssh est de s'assurer que tout ssh a son entrée standard attachée, par exemple en changeant

ssh otherhost some commands here

pour lire les commandes à partir d'un document ici, ce qui permet (pour ce scénario particulier) de bloquer l'entrée standard de l'application ssh pour les commandes :

ssh otherhost <<'____HERE'
    some commands here
____HERE

5voto

jacobm654321 Points 599

L'option ssh -n empêche de vérifier l'état de sortie de ssh lors de l'utilisation de HEREdoc tout en canalisant la sortie vers un autre programme. L'utilisation de /dev/null comme stdin est donc préférable.

#!/bin/bash
while read ONELINE ; do
   ssh ubuntu@host_xyz </dev/null <<EOF 2>&1 | filter_pgm 
   echo "Hi, $ONELINE. You come here often?"
   process_response_pgm 
EOF
   if [ ${PIPESTATUS[0]} -ne 0 ] ; then
      echo "aborting loop"
      exit ${PIPESTATUS[0]}
   fi
done << input_list.txt

4voto

Jonny Leeds Points 741

Cela m'arrivait parce que j'avais set -e et un grep dans une boucle revenait sans aucune sortie (ce qui donne un code d'erreur non nul).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X