Leer un archivo línea por línea asignando el valor a una variable


753

Tengo el siguiente archivo .txt:

Marco
Paolo
Antonio

Quiero leerlo línea por línea, y para cada línea quiero asignar un valor de línea .txt a una variable. Suponiendo que mi variable es$name , el flujo es:

  • Leer la primera línea del archivo
  • Asignar $name = "Marco"
  • Haz algunas tareas con $name
  • Leer la segunda línea del archivo
  • Asignar $name= "Paolo"


3
¿Pueden esas preguntas fusionarse de alguna manera? Ambos tienen algunas respuestas realmente buenas que resaltan diferentes aspectos del problema, las malas respuestas tienen explicaciones detalladas en los comentarios sobre lo que es malo sobre ellas, y a partir de ahora no se puede obtener una visión general completa sobre qué considerar, a partir de las respuestas de Una sola pregunta de la pareja. Sería útil tenerlo todo en un solo lugar, en lugar de distribuirlo en 2 páginas.
Egor Hans

Respuestas:


1357

Lo siguiente lee un archivo pasado como argumento línea por línea:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Esta es la forma estándar para leer líneas de un archivo en un bucle. Explicación:

  • IFS=(o IFS='') evita que se recorten los espacios en blanco iniciales / finales.
  • -r evita que se interpreten los escapes de barra invertida.

O puede ponerlo en un script de ayuda de archivo bash, contenido de ejemplo:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Si lo anterior se guarda en un script con nombre de archivo readfile, se puede ejecutar de la siguiente manera:

chmod +x readfile
./readfile filename.txt

Si el archivo no es un archivo de texto POSIX estándar (= no terminado por un carácter de nueva línea), el bucle puede modificarse para manejar líneas parciales finales:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Aquí, || [[ -n $line ]]evita que se ignore la última línea si no termina con un \n(ya queread devuelve un código de salida distinto de cero cuando encuentra EOF).

Si los comandos dentro del bucle también se leen desde la entrada estándar, el descriptor de archivo utilizado por se readpuede cambiar a otra cosa (evite los descriptores de archivo estándar ), por ejemplo:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Los shells que no son Bash pueden no saberlo read -u3; en su read <&3lugar, úselos)


23
Hay una advertencia con este método. Si algo dentro del bucle while es interactivo (por ejemplo, lecturas de stdin), tomará su entrada de $ 1. No tendrá la oportunidad de ingresar datos manualmente.
carpie

10
Cabe destacar que algunos comandos rompen (como en, rompen el ciclo) esto. Por ejemplo, sshsin la -nbandera efectivamente hará que escapes del bucle. Probablemente haya una buena razón para esto, pero me tomó un tiempo descubrir qué estaba causando que mi código fallara antes de descubrir esto.
Alex

66
como una línea: mientras IFS = '' read -r line || [[-n "$ line"]]; hacer eco "$ line"; hecho <nombre de archivo
Joseph Johnson

8
@ OndraŽižka, eso es causado por el ffmpegconsumo de stdin. Agregue </dev/nulla su ffmpeglínea y no podrá o usará un FD alternativo para el bucle. Ese enfoque de "FD alternativo" se parece while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy

99
refunfuñar re: aconsejando una .shextensión. Los ejecutables en UNIX no suelen tener extensiones (no se ejecuta ls.elf), y tener un bash shebang (y herramientas solo para bash como [[ ]]) y una extensión que implica la compatibilidad POSIX sh es internamente contradictorio.
Charles Duffy

309

Te animo a que uses la -rbandera readque significa:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Estoy citando de man 1 read.

Otra cosa es tomar un nombre de archivo como argumento.

Aquí está el código actualizado:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

44
Recorta el espacio
inicial

@Thomas y ¿qué pasa con los espacios en el medio? Sugerencia: intento de ejecución de comando no deseado.
kmarsh

1
Esto funcionó para mí, en contraste con la respuesta aceptada.
Neurotransmisor

3
@TranslucentCloud, si esto funcionaba y la respuesta aceptada no, sospecho que su concha era sh, no bash; El comando de prueba extendido utilizado en la || [[ -n "$line" ]]sintaxis en la respuesta aceptada es un bashism. Dicho esto, esa sintaxis en realidad tiene un significado pertinente: hace que el bucle continúe para la última línea en el archivo de entrada, incluso si no tiene una nueva línea. Si quisieras hacer eso de una manera compatible con POSIX, te gustaría || [ -n "$line" ]usarlo en [lugar de hacerlo [[.
Charles Duffy

3
Dicho esto, no todavía tienen que ser modificados para conjunto IFS=para el readpara evitar el recorte de los espacios en blanco.
Charles Duffy

132

El uso de la siguiente plantilla de Bash debería permitirle leer un valor a la vez de un archivo y procesarlo.

while read name; do
    # Do what you want to $name
done < filename

14
como una frase: mientras lee el nombre; hacer eco $ {nombre}; hecho <nombre de archivo
Joseph Johnson

44
@CalculusKnight, solo "funcionó" porque no usaste datos suficientemente interesantes para probar. Pruebe el contenido con barras invertidas o con una línea que solo contenga *.
Charles Duffy

77
@Matthias, las suposiciones que eventualmente resultan ser falsas son una de las mayores fuentes de errores, tanto de impacto en la seguridad como de otro tipo. El evento de pérdida de datos más grande que he visto se debió a un escenario que alguien supuso que "literalmente nunca aparecería": un desbordamiento del búfer que descarga memoria aleatoria en un búfer utilizado para nombrar archivos, lo que genera un script que hace suposiciones sobre los nombres que podrían existir tiene un comportamiento muy, muy desafortunado.
Charles Duffy

55
@Matthias, ... y eso es especialmente cierto aquí, ya que los ejemplos de código que se muestran en StackOverflow están destinados a ser utilizados como herramientas de enseñanza, ¡para que la gente reutilice los patrones en su propio trabajo!
Charles Duffy

55
@Matthias, estoy totalmente en desacuerdo con la afirmación de que "solo debe idear su código para los datos que espera". Casos inesperados son donde están sus errores, donde están sus vulnerabilidades de seguridad: manejarlos es la diferencia entre el código slapdash y el código robusto. De acuerdo, ese manejo no necesita ser sofisticado, solo puede ser "salir con un error", pero si no tiene ningún manejo, su comportamiento en casos inesperados no está definido.
Charles Duffy

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
No hay nada en contra de las otras respuestas, tal vez son más sofisticadas, pero voté esta respuesta porque es simple, legible y es suficiente para lo que necesito. Tenga en cuenta que, para que funcione, el archivo de texto a leer debe terminar con una línea en blanco (es decir, se debe presionar Enterdespués de la última línea); de lo contrario, se ignorará la última línea. Al menos eso es lo que me pasó.
Antonio Vinicius Menezes Medei

12
¿Uso inútil del gato, shurely?
Brian Agnew

55
Y la cita está rota; y no debe usar nombres de variables en mayúscula porque están reservados para uso del sistema.
tripleee

77
@AntonioViniciusMenezesMedei, ... además, he visto a personas sufrir pérdidas financieras porque asumieron que estas advertencias nunca les importarían; no aprendió buenas prácticas; y luego siguió los hábitos a los que estaban acostumbrados cuando escribían scripts que administraban copias de seguridad de datos críticos de facturación. Aprender a hacer las cosas bien es importante.
Charles Duffy

66
Otro problema aquí es que la tubería abre una nueva subshell, es decir, todas las variables establecidas dentro del bucle no se pueden leer una vez que finaliza el bucle.
mxmlnkn

20

Muchas personas han publicado una solución que está demasiado optimizada. No creo que sea incorrecto, pero humildemente creo que sería deseable una solución menos optimizada para permitir que todos entiendan fácilmente cómo está funcionando esto. Aquí está mi propuesta:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

Utilizar:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Si ha configurado de manera IFSdiferente, obtendrá resultados extraños.


34
Este es un método horrible . Por favor, no lo use a menos que quiera tener problemas con el glob que ocurrirá antes de darse cuenta.
gniourf_gniourf

13
@MUYBelgium, ¿intentaste con un archivo que contiene un solo *en una línea? De todos modos, este es un antipatrón . No lea líneas con for .
gniourf_gniourf

2
@ OndraŽižka, el readenfoque es el enfoque de mejores prácticas por consenso de la comunidad . La advertencia que menciona en su comentario es una que se aplica cuando su ciclo ejecuta comandos (como ffmpeg) que leen desde stdin, resueltos trivialmente utilizando un FD que no sea stdin para el ciclo o redirigiendo la entrada de dichos comandos. Por el contrario, evitar el error global en su forenfoque de bucle significa hacer (y luego tener que revertir) los cambios de configuración global de shell.
Charles Duffy

1
@ OndraŽižka, ... además, el forenfoque de bucle que usa aquí significa que todo el contenido debe leerse antes de que el bucle pueda comenzar a ejecutarse, lo que lo hace completamente inutilizable si está pasando por gigabytes de datos, incluso si ha deshabilitado pegajoso el while readbucle no necesita almacenar más de una sola línea de datos a la vez, lo que significa que puede comenzar a ejecutarse mientras el subproceso genera contenido todavía en ejecución (por lo tanto, puede utilizarse para fines de transmisión), y también ha limitado el consumo de memoria.
Charles Duffy

1
En realidad, los whileenfoques basados ​​en pares parecen tener problemas con los caracteres *. Vea los comentarios de la respuesta aceptada arriba. Sin embargo, no estoy discutiendo en contra de la iteración por archivos que son un antipatrón.
Egor Hans

9

Si necesita procesar tanto el archivo de entrada como la entrada del usuario (o cualquier otra cosa de stdin), use la siguiente solución:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

Basado en la respuesta aceptada y en el tutorial de redirección de bash-hackers .

Aquí, abrimos el descriptor de archivo 3 para el archivo pasado como argumento de script y le pedimos readque use este descriptor como input ( -u 3). Por lo tanto, dejamos el descriptor de entrada predeterminado (0) adjunto a un terminal u otra fuente de entrada, capaz de leer la entrada del usuario.


7

Para el manejo adecuado de errores:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

¿Podría por favor agregar alguna explicación?
Tyler Christian

Lamentablemente se pierde la última línea del archivo.
ungalcrys

... y también, debido a la falta de comillas, las líneas de munges que contienen comodines, como se describe en BashPitfalls # 14 .
Charles Duffy

0

Lo siguiente solo imprimirá el contenido del archivo:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Esta respuesta realmente no agrega nada sobre las respuestas existentes, no funciona debido a un error tipográfico / error y se rompe de muchas maneras.
Konrad Rudolph

0

Use la herramienta IFS (separador de campo interno) en bash, define el carácter que usa para separar las líneas en tokens, por defecto incluye < tab > / < space > / < newLine >

Paso 1 : Cargue los datos del archivo e insértelos en la lista:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

Paso 2 : ahora itera e imprime la salida:

for line in "${array[@]}"
  do
    echo "$line"
  done

echo índice específico en la matriz : Acceso a una variable en la matriz:

echo "${array[0]}"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.