Comment utiliser` while read ' (Bash) pour lire la dernière ligne d'un fichier s'il n'y a 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, cela sautera effectivement la dernière ligne.

j'ai cherché partout une solution et j'ai trouvé ce:

quand read atteint la fin du fichier à la place de fin de ligne, il lit dans le données et les affecter aux variables, mais il sort avec un état différent de zéro. Si votre boucle est construite "alors lire ;ne trucs ;fait

donc au lieu de tester la sortie read statut directement, tester un drapeau, et avoir la commande read a placé ce drapeau de dans le corps de la boucle. De cette façon indépendamment de lit statut de sortie, la le corps entier de la boucle fonctionne, parce que lire était juste une liste de commandes dans la boucle comme les autres, pas un facteur décisif de si la boucle obtenez de course à tous.

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 while boucle que j'avais plus tôt, c'est-à-dire sans codage dur de l'emplacement du fichier d'entrée?

41
demandé sur Mathias Bynens 2010-11-12 16:34:29

7 réponses

dans votre premier exemple, je suppose que vous lisez stdin. Pour faire la même chose avec le deuxième bloc de code, vous avez juste à supprimer la redirection et echo $REPLY:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
12
répondu netcoder 2010-11-12 13:41:40

j'utilise la construction suivante:

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

il fonctionne avec à peu près tout sauf les caractères nuls dans l'entrée:

  • fichiers qui commencent ou se terminent par des lignes vides
  • les Lignes qui commencent ou se terminent par des espaces
  • les Fichiers qui n'ont pas de terminaison de retour à la ligne
29
répondu Adam Bryzak 2015-06-10 11:46:22

en utilisant grep avec une boucle while:

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

en utilisant grep . au lieu de grep "" ignorer les lignes vides.

Remarque:

  1. en utilisant IFS= garde n'importe quelle indentation de ligne intacte.

  2. Vous devriez toujours utiliser l'option-r à lire.

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

3
répondu Jahid 2015-07-14 05:04:16

au Lieu de lire, essayez d'utiliser GNU Coreutils,chat, etc.

stdin

readvalue=$(tee)
echo $readvalue

dans le fichier

readvalue=$(cat filename)
echo $readvalue
2
répondu prabhakaran9397 2016-11-03 14:29:35

La question fondamentale ici est que read renvoie errorlevel 1 quand il rencontre EOF, même s'il alimente correctement la variable.

vous pouvez donc utiliser errorlevel de read tout de suite dans votre boucle, otherwize, les dernières données ne seront pas analysées. Mais vous pourriez 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 souhaitez une très bonne façon d'analyser vos lignes, vous devez utiliser:

IFS='' read -r LINE

n'oubliez pas que:

  • nul caractère sera ignoré
  • si vous vous en tenez à l'aide de echo pour imiter le comportement de cat vous aurez besoin de force echo -n dès détection D'EOF (vous pouvez utiliser la condition [ "$eof" == true ])
1
répondu vaab 2015-01-03 09:18:48

la réponse de@Netcoder est bonne, cette optimisation élimine les lignes vierges intempestives, permet également à la dernière ligne de ne pas avoir de nouvelle ligne, si c'est ainsi que l'original était.

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

j'ai utilisé une variante de ceci pour créer 2 fonctions pour permettre la tuyauterie du texte qui inclut 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
 }

(en passant - que $1 permet de tuyau sinon, il suffit de se traduit par des arguments)

0
répondu unsynchronized 2016-06-07 09:44:34

C'est le modèle que j'ai utilisé:

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

ce qui fonctionne parce que même les while boucle se termine par le "test" de la read retourne une valeur non-nulle code, read occupe toujours la variable intégrée $REPLY (ou toutes les variables que vous choisissez d'assigner avec