Leer un archivo orientado a líneas que puede no terminar con una nueva línea

11

Tengo un archivo llamado /tmp/urlFiledonde cada línea representa una url. Estoy tratando de leer del archivo de la siguiente manera:

cat "/tmp/urlFile" | while read url
do
    echo $url
done

Si la última línea no termina con un carácter de nueva línea, esa línea no se leerá. Me preguntaba por qué?

¿Es posible leer todas las líneas, independientemente de si terminaron con una nueva línea o no?

Tim
fuente
8
Se discute en ¿Por qué usar un bucle de shell para procesar texto se considera una mala práctica? (con alguna forma de hacerlo)
Stéphane Chazelas
2
Hah @ Stéphane Me gusta el TBD allí ;-).
Stephen Kitt el
2
Otra forma de agregar la nueva línea final si falta; awk 1 /tmp/urlFile.. soawk 1 /tmp/urlFile | while ...
muru
@muru, esa es una mejor respuesta que cualquier otra aquí.
Comodín el
1
Como se pregunta por qué no se lee: stackoverflow.com/a/729795/1968
Konrad Rudolph el

Respuestas:

13

Tu harías:

while IFS= read -r url || [ -n "$url" ]; do
  printf '%s\n' "$url"
done < url.list

(efectivamente, ese bucle vuelve a agregar la nueva línea que falta en la última línea (no)).

Ver también:

Stéphane Chazelas
fuente
Gracias. Leí los artículos vinculados, y tal vez me pierdo algo, ¿por qué "ese ciclo agrega la nueva línea faltante en la última (no) línea"?
Tim
1
@Tim Lo que Stephane parece querer decir es que agrega la nueva línea faltante en la salida, ya que todas las printfllamadas aquí tienen \n.
Sergiy Kolodyazhnyy
6

Esto parece resolverse en parte con readarray -t:

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    printf '%s\n' "$url"
done

Sin embargo, tenga en cuenta que si bien esto funciona para archivos de tamaño razonable, esta solución presenta un nuevo problema potencial con archivos muy grandes: primero lee el archivo en una matriz que luego debe iterarse. Para archivos muy grandes, esto puede llevar mucho tiempo y memoria, potencialmente hasta el punto de falla.

DopeGhoti
fuente
Gracias. ¿Qué parte resuelve y cuál no?
Tim
Resuelve el problema con la falta de una nueva línea final, pero introduce un nuevo problema potencial con archivos muy grandes, porque primero lee el archivo en una matriz que luego debe iterarse.
DopeGhoti
1
@DopeGhoti Esa es buena información. ¿Puedo sugerirle que la agregue directamente a la respuesta?
RJHunter
La respuesta ha sido modificada.
DopeGhoti
5

Por definición , un archivo de texto consiste en una secuencia de líneas. Una línea termina con un carácter de nueva línea. Por lo tanto, un archivo de texto termina con un carácter de nueva línea, a menos que esté vacío.

El readbuiltin solo está destinado a leer archivos de texto. No está pasando un archivo de texto, por lo que no puede esperar que funcione sin problemas. El shell lee todas las líneas; lo que se salta son los caracteres adicionales después de la última línea.

Si tiene un archivo de entrada potencialmente malformado que le puede faltar su última línea, puede agregarle una nueva línea, solo para estar seguro.

{ cat "/tmp/urlFile"; echo; } | 

Los archivos que deberían ser archivos de texto pero que faltan en la nueva línea final a menudo son producidos por editores de Windows. Esto generalmente se combina con las terminaciones de línea de Windows, que son CR LF, en oposición a las LF de Unix. Los caracteres CR rara vez son útiles en cualquier lugar, y no pueden aparecer en las URL en ningún caso, por lo que debe eliminarlos.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | 

En caso de que el archivo de entrada esté bien formado y termine con una nueva línea, echoagrega una línea en blanco adicional. Como las URL no pueden estar vacías, simplemente ignore las líneas en blanco.

Tenga en cuenta también que readno lee líneas de una manera directa. Ignora los espacios en blanco iniciales y finales, lo que para una URL es probablemente deseable. Trata la barra invertida al final de una línea como un carácter de escape, lo que hace que la siguiente línea se una con la primera menos la secuencia barra invertida-nueva línea, lo que definitivamente no es deseable. Por lo tanto, debe pasar la -ropción a read. Es muy, muy raro readque sea lo correcto en lugar de hacerlo read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Gilles 'SO- deja de ser malvado'
fuente
3

Bueno, readdevuelve un valor falso si se encuentra con el final del archivo antes de una nueva línea, pero incluso si lo hace, todavía asigna el valor que leyó. Por lo tanto, podemos verificar si la llamada final de readdevuelve algo más que una línea vacía y procesarla de manera normal. Entonces, solo salga del ciclo después de que readdevuelva falso y la línea esté vacía:

#!/bin/sh
while IFS= read -r line || [ "$line" ]; do 
    echo "line: $line"
done

$ printf 'foo\nbar' | sh ./read.sh 
line: foo
line: bar
$ printf 'foo\nbar\n' | sh ./read.sh 
line: foo
line: bar
ilkkachu
fuente
1

Otra forma sería así:

Cuando la lectura alcanza el final del archivo en lugar del final de la línea, sí lee los datos y los asigna a las variables, pero sale con un estado distinto de cero. Si su ciclo está construido "mientras lee; haga cosas; hecho

Entonces, en lugar de probar el estado de salida de lectura directamente, pruebe un indicador y haga que el comando de lectura establezca ese indicador desde dentro del cuerpo del bucle. De esa manera, independientemente del estado de salida de las lecturas, se ejecuta todo el cuerpo del bucle, porque la lectura era solo una de las listas de comandos en el bucle como cualquier otra, no un factor decisivo de si el bucle se ejecutará en absoluto.

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY 
done < /tmp/urlFile

Referido desde aquí .

Hunter.S.Thompson
fuente
1
cat "/ tmp / urlFile" | mientras lee url
hacer
    echo $ url
hecho

Este es un uso inútil decat .

Irónicamente, puede reemplazar el catproceso aquí con algo realmente útil: una herramienta que tienen los sistemas POSIX para agregar la nueva línea faltante y convertir el archivo en un archivo de texto POSIX adecuado.

sed -e '$ a \' "/ tmp / urlFile" | mientras lee -r url
hacer
    printf "% s \ n" "$ {url}"
hecho

Otras lecturas

JdeBP
fuente
1
POSIX no especifica el comportamiento de sed cuando la entrada no termina en un carácter de nueva línea; también cuando hay líneas más grandes que LINE_MAX, mientras que el comportamiento de readse especifica en esos casos.
Stéphane Chazelas