web-dev-qa-db-fra.com

Comment utiliser `while read` (Bash) pour lire la dernière ligne d’un fichier s’il n’ya pas de nouvelle ligne à la fin du fichier?

Disons que j’ai le script Bash suivant:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

J'ai remarqué que pour les fichiers sans nouvelle ligne à la fin, la dernière ligne sera ignorée.

J'ai cherché une solution et j'ai trouvé ceci :

Lorsque la lecture atteint la fin du fichier à la place de fin de ligne, il est indiqué dans le données et les attribuer aux variables, mais il existe avec un statut différent de zéro . Si votre boucle est construite "alors que Lisez; faites des choses; fini

Donc, au lieu de tester la sortie de lecture statut directement, tester un drapeau et avoir la commande de lecture a défini cet indicateur à partir de dans le corps de la boucle. De cette façon quel que soit l'état de sortie de la lecture, le tout le corps de la boucle s'exécute, car read était juste une de la liste de commandes dans la boucle comme un autre, pas un facteur décisif de si la boucle sera être couru du tout.

DONE=false
until $DONE ;do
read || DONE=true
# process $REPLY here
done < /path/to/file.in

Comment puis-je réécrire cette solution pour qu’elle se comporte exactement comme la boucle while que j’avais précédemment, c’est-à-dire sans codage en dur de l’emplacement du fichier d’entrée?

43
Mathias Bynens

Dans votre premier exemple, je suppose que vous lisez stdin. Pour faire la même chose avec le deuxième bloc de code, il vous suffit de supprimer la redirection et d’écho $ REPLY:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
13
netcoder

J'utilise la construction suivante:

while IFS= read -r LINE || [[ -n "$LINE" ]]; do
    echo "$LINE"
done

Cela fonctionne avec pratiquement tout sauf les caractères nuls dans l'entrée:

  • Fichiers commençant ou se terminant par des lignes vierges
  • Lignes qui commencent ou se terminent par des espaces
  • Fichiers qui n'ont pas de nouvelle ligne de terminaison
31
Adam Bryzak

Utilisation de grep avec la boucle while:

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

Utiliser grep . au lieu de grep "" sautera les lignes vides.

Remarque: 

  1. L'utilisation de IFS= conserve l'indentation de ligne intacte.

  2. Vous devriez presque toujours utiliser l'option -r avec read.

  3. Un fichier sans nouvelle ligne à la fin n'est pas un fichier texte unix standard.

4
Jahid

Au lieu de read , essayez d’utiliser GNU Coreutils like tee , cat , etc.

de stdin

readvalue=$(tee)
echo $readvalue

de fichier

readvalue=$(cat filename)
echo $readvalue
2
prabhakaran9397

Le problème fondamental ici est que read renvoie le niveau d'erreur 1 lorsqu'il rencontre EOF, même s'il alimente toujours correctement la variable.

Ainsi, vous pouvez utiliser le niveau d'erreur read tout de suite dans votre boucle. Sinon, les dernières données ne seront pas analysées. Mais vous pouvez faire ceci:

eof=
while [ -z "$eof" ]; do
    read SCRIPT_SOURCE_LINE || eof=true   ## detect eof, but have a last round
    echo "$SCRIPT_SOURCE_LINE"
done

Si vous voulez un moyen très solide d’analyser vos lignes, vous devez utiliser:

IFS='' read -r LINE

Rappelez-vous que:

  • Le caractère NUL sera ignoré
  • si vous vous en tenez à utiliser echo pour imiter le comportement de cat, vous devrez forcer un echo -n à EOF détecté (vous pouvez utiliser la condition [ "$eof" == true ])
1
vaab

La réponse de @ Netcoder est bonne, cette optimisation élimine les fausses lignes vierges et permet également à la dernière ligne de ne pas avoir de saut de ligne, si tel était l'original.

DONE=false
NL=
until $DONE ;do
if ! read ; then DONE=true ; NL='-n ';fi
echo $NL$REPLY
done

J'ai utilisé une variante de cela pour créer 2 fonctions permettant de rediriger du texte avec un '[' pour garder grep heureux. (vous pouvez ajouter d'autres traductions)

function grepfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\[/\\\[}
      done
    else
      echo "${x//\[/\\\[}"
    fi
 }


 function grepunfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\\\[/\[}
      done
    else
      echo "${x//\\\[/\[}"
    fi
 }

(passant - comme $ 1 active le pipe sinon traduit uniquement les arguments)

0
unsynchronized

C'est le motif que j'ai utilisé:

while read -r; do
  echo "${REPLY}"
done
[[ ${REPLY} ]] && echo "${REPLY}"

Ce qui fonctionne car même si la boucle while se termine avec le "test" de la read avec un code différent de zéro, read remplit toujours la variable incorporée $REPLY (ou les variables que vous choisissez d'attribuer avec read).

0
Olli K