¿Cómo usar 'while read' (Bash) para leer la última línea de un archivo si no hay una nueva línea al final del archivo?


Digamos que tengo el siguiente script Bash:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

Me di cuenta de que para los archivos sin una nueva línea al final, esto saltará efectivamente la última línea.

He buscado una solución y he encontrado esto :

Cuando read llega al final del archivo de fin de línea, se lee en el datos y asignarlos a las variables, pero sale con un estado distinto de cero. Si su bucle está construido " mientras leer, hacer cosas ;hecho

So en lugar de probar la salida de lectura estado directamente, probar una bandera, y tener el comando read establece ese indicador desde dentro del cuerpo del bucle. De esa manera independientemente del estado de salida de las lecturas, todo el cuerpo del bucle se ejecuta, porque leer era solo uno de la lista de comandos en el bucle como cualquier otro, no factor decisivo de si el bucle se vete corriendo.

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

¿Cómo puedo reescribir esta solución para que se comporte exactamente igual que el bucle while que estaba teniendo antes, es decir, sin hardcoding la ubicación del archivo de entrada?

Author: Mathias Bynens, 2010-11-12

7 answers

En tu primer ejemplo, asumo que estás leyendo desde stdin. Para hacer lo mismo con el segundo bloque de código, solo tienes que eliminar la redirección y echo REPLY REPLY:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
 12
Author: netcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-12 13:41:40

Utilizo la siguiente construcción:

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

Funciona con casi cualquier cosa excepto caracteres nulos en la entrada:

  • Archivos que comienzan o terminan con líneas en blanco
  • Líneas que comienzan o terminan con espacios en blanco
  • Archivos que no tienen una nueva línea de terminación
 29
Author: Adam Bryzak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-10 11:46:22

Usando grep con bucle while:

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

Usando grep . en lugar de grep "" saltará las líneas vacías.

Nota:

  1. Usar IFS= mantiene intacta cualquier sangría de línea.

  2. Casi siempre debe usar la opción-r con read.

  3. El archivo sin una nueva línea al final no es un archivo de texto estándar de unix.

 3
Author: Jahid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-14 05:04:16

En Lugar de leer, pruebe a utilizar GNU Coreutils como tee, cat, etc.

De stdin

readvalue=$(tee)
echo $readvalue

Desde el archivo

readvalue=$(cat filename)
echo $readvalue
 2
Author: prabhakaran9397,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-03 14:29:35

El problema básico aquí es que read devolverá errorlevel 1 cuando encuentre EOF, incluso si todavía alimentará correctamente la variable.

Así que puede usar errorlevel de read de inmediato en su bucle, otherwize, los últimos datos no se analizarán. Pero usted podría hacer esto:

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

Si desea una forma muy sólida de analizar sus líneas, debe usar:

IFS='' read -r LINE

Recuerda que:

  • El carácter NUL será ignorado
  • si sigues usando echo para imitar el comportamiento de cat tendrá que forzar un echo -n sobre EOF detectado (puede usar la condición [ "$eof" == true ])
 1
Author: vaab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-03 09:18:48

La respuesta de@Netcoder es buena, esta optimización elimina las líneas en blanco espurias, también permite que la última línea no tenga una nueva línea, si así es como era la original.

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

Usé una variante de esto para crear 2 funciones para permitir la canalización de texto que incluye un '[' para mantener a grep feliz. (puede agregar otras traducciones)

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
 }

(passing-as enables 1 habilita pipe de lo contrario solo traduce argumentos)

 0
Author: unsynchronized,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 09:44:34

Este es el patrón que he estado usando:

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

Que funciona porque incluso tho' el bucle while termina como la "prueba" de la read sale con un código distinto de cero, read todavía llena la variable incorporada $REPLY (o cualquier variable que elija asignar con read).

 0
Author: Olli K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-06 17:24:58