Recorrer el contenido de un archivo en Bash
Frecuentes
Visto 1,868 equipos
1562
¿Cómo recorro cada línea de un archivo de texto con Asestar un golpe?
Con este guión:
echo "Start!"
for p in (peptides.txt)
do
echo "${p}"
done
Obtengo esta salida en la pantalla:
Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'
(Más tarde quiero hacer algo más complicado con $p
que solo enviar a la pantalla).
La variable de entorno SHELL es (de env):
SHELL=/bin/bash
/bin/bash --version
salida:
GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
cat /proc/version
salida:
Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006
El archivo peptides.txt contiene:
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
14 Respuestas
2339
Una forma de hacerlo es:
while read p; do
echo "$p"
done <peptides.txt
Como se señaló en los comentarios, esto tiene los efectos secundarios de recortar los espacios en blanco iniciales, interpretar las secuencias de barra invertida y omitir la última línea si falta un salto de línea de terminación. Si estas son inquietudes, puede hacer lo siguiente:
while IFS="" read -r p || [ -n "$p" ]
do
printf '%s\n' "$p"
done < peptides.txt
Excepcionalmente, si el el cuerpo del bucle puede leer desde la entrada estándar, puede abrir el archivo usando un descriptor de archivo diferente:
while read -u 10 p; do
...
done 10<peptides.txt
Aquí, 10 es solo un número arbitrario (diferente de 0, 1, 2).
Respondido 31 Oct 19, 17:10
¿Cómo debo interpretar la última línea? El archivo peptides.txt se redirige a la entrada estándar y, de alguna manera, a todo el bloque while. - Pedro Mortensen
"Slurp peptides.txt en este bucle while, para que el comando 'leer' tenga algo que consumir". Mi método "cat" es similar, enviando la salida de un comando al bloque while para ser consumido por 'leer', también, solo que lanza otro programa para hacer el trabajo. - Warren Young
Este método parece omitir la última línea de un archivo. - xastor
¡¡Cita dos veces las líneas !! echo "$ p" y el archivo .. confía en mí, te morderá si no lo haces !!! ¡SÉ! jajaja - mike q
Ambas versiones no pueden leer una línea final si no se termina con una nueva línea. Siempre utilizan el while read p || [[ -n $p ]]; do ...
- dawg
540
cat peptides.txt | while read line
do
# do something with $line here
done
y la variante de una sola línea:
cat peptides.txt | while read line; do something_with_$line_here; done
Estas opciones omitirán la última línea del archivo si no hay un avance de línea final.
Puede evitar esto de la siguiente manera:
cat peptides.txt | while read line || [[ -n $line ]];
do
# do something with $line here
done
Respondido 31 Oct 19, 17:10
En general, si usa "gato" con un solo argumento, está haciendo algo mal (o subóptimo). - JesperE
Sí, simplemente no es tan eficiente como el de Bruno, porque lanza otro programa, innecesariamente. Si la eficiencia importa, hágalo a la manera de Bruno. Recuerdo mi camino porque puedes usarlo con otros comandos, donde la sintaxis "redireccionar desde" no funciona. - Warren Young
Hay otro problema más serio con esto: debido a que el bucle while es parte de una tubería, se ejecuta en una subcapa y, por lo tanto, cualquier variable establecida dentro del bucle se pierde cuando sale (consulte bash-hackers.org/wiki/doku.php/mirroring/bashfaq/024). Esto puede ser muy molesto (dependiendo de lo que intente hacer en el ciclo). - gordon davisson
Utilizo "archivo cat |" como inicio de muchos de mis comandos simplemente porque a menudo hago prototipos con "archivo head |" - estera kelcey
Puede que esto no sea tan eficiente, pero es mucho más legible que otras respuestas. - Lector salvaje
154
Opción 1a: Bucle while: línea única a la vez: redirección de entrada
#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do
echo $p
done < $filename
Opción 1b: Bucle while: línea única a la vez:
Abra el archivo, lea desde un descriptor de archivo (en este caso, el descriptor de archivo # 4).
#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
echo $p
done
Respondido el 18 de Septiembre de 19 a las 10:09
Para la opción 1b: ¿es necesario volver a cerrar el descriptor de archivo? Por ejemplo, el bucle podría ser un bucle interno. - Pedro Mortensen
El descriptor de archivo se limpiará con las salidas del proceso. Se puede realizar un cierre explícito para reutilizar el número fd. Para cerrar un fd, use otro exec con la sintaxis & -, así: exec 4 <& - - Stan tumbas
Gracias por la Opción 2. Me encontré con grandes problemas con la Opción 1 porque necesitaba leer desde stdin dentro del ciclo; en tal caso, la Opción 1 no funcionará. - masgo
Debería señalar más claramente que la Opción 2 es fuertemente desanimado. @masgo Opción 1b debería funcionar en ese caso, y se puede combinar con la sintaxis de redirección de entrada de la Opción 1a reemplazando done < $filename
a done 4<$filename
(que es útil si desea leer el nombre del archivo de un parámetro de comando, en cuyo caso puede simplemente reemplazar $filename
by $1
). - egor hans
Necesito recorrer el contenido del archivo como tail -n +2 myfile.txt | grep 'somepattern' | cut -f3
, mientras se ejecutan comandos ssh dentro del bucle (consume stdin); ¿La opción 2 aquí parece ser la única forma? - user5359531
99
Esta no es mejor que otras respuestas, pero es una forma más de hacer el trabajo en un archivo sin espacios (ver comentarios). Encuentro que a menudo necesito frases breves para buscar en listas en archivos de texto sin el paso adicional de usar archivos de script separados.
for word in $(cat peptides.txt); do echo $word; done
Este formato me permite ponerlo todo en una línea de comandos. Cambie la parte "echo $ word" a lo que desee y podrá emitir varios comandos separados por punto y coma. El siguiente ejemplo utiliza el contenido del archivo como argumentos en otros dos scripts que puede haber escrito.
for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done
O si tiene la intención de usar esto como un editor de flujo (aprender sed), puede volcar la salida a otro archivo de la siguiente manera.
for word in $(cat peptides.txt); do cmd_a.sh $word; cmd_b.py $word; done > outfile.txt
Los he usado como se escribió anteriormente porque he usado archivos de texto donde los he creado con una palabra por línea. (Ver comentarios) Si tiene espacios en los que no desea dividir sus palabras / líneas, se vuelve un poco más feo, pero el mismo comando aún funciona de la siguiente manera:
OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS
Esto solo le dice al shell que se divida solo en líneas nuevas, no en espacios, luego devuelve el entorno a lo que era anteriormente. Sin embargo, en este punto, es posible que desee considerar ponerlo todo en un script de shell en lugar de comprimirlo todo en una sola línea.
¡Mucha suerte!
Respondido el 22 de diciembre de 13 a las 15:12
La fiesta $ ( maxpolk
@ JoaoCosta, maxpolk: Buenos puntos que no había considerado. He editado la publicación original para reflejarlos. ¡Gracias! - mayordomo
Usar for
hace que los tokens / líneas de entrada estén sujetos a expansiones de shell, lo que generalmente no es deseable; prueba esto: for l in $(echo '* b c'); do echo "[$l]"; done
- como verás, el *
- aunque originalmente un citado literal: se expande a los archivos del directorio actual. - mklement0
@dblanchard: El último ejemplo, usando $ IFS, debería ignorar los espacios. ¿Has probado esa versión? - mayordomo
La forma en que este comando se vuelve mucho más complejo a medida que se solucionan los problemas cruciales, presenta muy bien por qué usar for
iterar líneas de archivo es una mala idea. Además, el aspecto de expansión mencionado por @ mklement0 (aunque eso probablemente se pueda eludir al incluir comillas de escape, lo que nuevamente hace que las cosas sean más complejas y menos legibles). - egor hans
81
Algunas cosas más no cubiertas por otras respuestas:
Leyendo de un archivo delimitado
# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
# process the fields
# if the line has less than three fields, the missing fields will be set to an empty string
# if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt
Leer de la salida de otro comando, usando sustitución de proceso
while read -r line; do
# process the line
done < <(command ...)
Este enfoque es mejor que command ... | while read -r line; do ...
porque el ciclo while aquí se ejecuta en el shell actual en lugar de en un subshell como en el caso de este último. Ver la publicación relacionada Una variable modificada dentro de un bucle while no se recuerda.
Lectura de una entrada delimitada por nulos, por ejemplo find ... -print0
while read -r -d '' line; do
# logic
# use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)
Lectura relacionada: BashFAQ / 020 - ¿Cómo puedo encontrar y manejar de forma segura los nombres de archivo que contienen nuevas líneas, espacios o ambos?
Leer de más de un archivo a la vez
while read -u 3 -r line1 && read -u 4 -r line2; do
# process the lines
# note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt
Basado en @ chepner's https://www.youtube.com/watch?v=xB-eutXNUMXJtA&feature=youtu.be aquí:
-u
es una extensión de bash. Para la compatibilidad POSIX, cada llamada se vería como read -r X <&3
.
Leer un archivo completo en una matriz (versiones de Bash anteriores a la 4)
while read -r line; do
my_array+=("$line")
done < my_file
Si el archivo termina con una línea incompleta (falta una nueva línea al final), entonces:
while read -r line || [[ $line ]]; do
my_array+=("$line")
done < my_file
Leer un archivo completo en una matriz (versiones de Bash 4x y posteriores)
readarray -t my_array < my_file
or
mapfile -t my_array < my_file
Y entonces
for line in "${my_array[@]}"; do
# process the lines
done
Más sobre las incorporaciones de shell
read
yreadarray
comandos - GNU- BashFAQ / 001 - ¿Cómo puedo leer un archivo (flujo de datos, variable) línea por línea (y / o campo por campo)?
Artículos Relacionados:
Respondido 26 Oct 18, 06:10
tenga en cuenta que en lugar de command < input_filename.txt
siempre puedes hacer input_generating_command | command
or command < <(input_generating_command)
- maestroxilo
Gracias por leer el archivo en la matriz. Exactamente lo que necesito, porque necesito que cada línea se analice dos veces, agregue nuevas variables, haga algunas validaciones, etc. franco_108
esta es, con mucho, la versión más útil, creo: user5359531
46
Use un ciclo while, como este:
while IFS= read -r line; do
echo "$line"
done <file
Notas
Si no configura el
IFS
correctamente, perderá la sangría.
respondido 29 mar '17, 01:03
@ DavidC.Rankin La opción -r evita la interpretación de barra invertida. Note #2
es un enlace donde se describe en detalle ... - Yahid
Combine esto con la opción "leer -u" en otra respuesta y luego es perfecto. - Andrei Florin
@FlorinAndrei: El ejemplo anterior no necesita el -u
opción, ¿estás hablando de otro ejemplo con -u
? - Yahid
Revisé sus enlaces y me sorprendió que no hubiera una respuesta que simplemente vincule su enlace en la Nota 2. Esa página proporciona todo lo que necesita saber sobre ese tema. ¿O se desalientan las respuestas de solo enlace o algo así? - egor hans
@EgorHans: las respuestas de solo enlace generalmente se eliminan. - Yahid
14
Suponga que tiene este archivo:
$ cat /tmp/test.txt
Line 1
Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space
Line 6 has no ending CR
Hay cuatro elementos que alterarán el significado de la salida del archivo leído por muchas soluciones Bash:
- La línea en blanco 4;
- Espacios iniciales o finales en dos líneas;
- Mantener el significado de las líneas individuales (es decir, cada línea es un registro);
- La línea 6 no terminó con un CR.
Si desea que el archivo de texto línea por línea incluya líneas en blanco y líneas de terminación sin CR, debe usar un bucle while y debe tener una prueba alternativa para la línea final.
Estos son los métodos que pueden cambiar el archivo (en comparación con lo que cat
devoluciones):
1) Pierde la última línea y los espacios iniciales y finales:
$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
(Si lo haces while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
en su lugar, conserva los espacios iniciales y finales pero aún pierde la última línea si no termina con CR)
2) Uso de la sustitución de procesos con cat
leerá todo el archivo de un solo trago y pierde el significado de las líneas individuales:
$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
Line 2 has leading space
Line 3 followed by blank line
Line 5 (follows a blank line) and has trailing space
Line 6 has no ending CR'
(Si quita el "
desde $(cat /tmp/test.txt)
lee el archivo palabra por palabra en lugar de un trago. También probablemente no sea lo que se pretende ...)
La forma más sólida y sencilla de leer un archivo línea por línea y conservar todo el espacio es:
$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
' Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space '
'Line 6 has no ending CR'
Si desea eliminar los espacios iniciales y comerciales, elimine el IFS=
parte:
$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'
(Un archivo de texto sin terminación \n
, aunque es bastante común, se considera roto en POSIX. Si puedes contar con el final \n
usted no necesita || [[ -n $line ]]
en el capítulo respecto a la while
círculo.)
Más en el Preguntas frecuentes de BASH
contestado el 02 de mayo de 17 a las 18:05
13
Si no desea que su lectura se interrumpa por un carácter de nueva línea, use -
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
echo "$line"
done < "$1"
Luego ejecute el script con el nombre del archivo como parámetro.
respondido 08 mar '16, 16:03
4
#!/bin/bash
#
# Change the file name from "test" to desired input file
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
echo $x
done
respondido 24 mar '14, 17:03
Esta respuesta necesita las advertencias mencionadas en la respuesta de mightypile, y puede fallar gravemente si alguna línea contiene metacaracteres de shell (debido al "$ x" sin comillas). - Toby Speight
De hecho, me sorprende que la gente aún no haya inventado lo No lea líneas con for... - egor hans
3
Aquí está mi ejemplo de la vida real sobre cómo hacer un bucle de líneas de salida de otro programa, verificar subcadenas, eliminar comillas dobles de la variable, usar esa variable fuera del bucle. Supongo que muchos se hacen estas preguntas tarde o temprano.
##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
echo ParseFPS $line
FPS=parse
fi
if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
echo ParseFPS $line
FPS=${line##*=}
FPS="${FPS%\"}"
FPS="${FPS#\"}"
fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then
echo ParseFPS Unknown frame rate
fi
echo Found $FPS
Declarar la variable fuera del bucle, establecer el valor y usarlo fuera del bucle requiere hecho <<< "$ (...)" sintaxis. La aplicación debe ejecutarse en el contexto de la consola actual. Las comillas alrededor del comando mantienen nuevas líneas de flujo de salida.
Coincidencia de bucle para subcadenas y luego lee Nombre = valor par, divide la parte derecha del último = carácter, elimina la primera cita, elimina la última cita, tenemos un valor limpio para usar en otros lugares.
Respondido el 30 de junio de 15 a las 09:06
Si bien la respuesta es correcta, entiendo cómo terminó aquí. El método esencial es el mismo propuesto por muchas otras respuestas. Además, se ahoga por completo en su ejemplo de FPS. - egor hans
2
Me gusta usar xargs
en lugar de while
. xargs
es potente y compatible con la línea de comandos
cat peptides.txt | xargs -I % sh -c "echo %"
Con xargs
, también puede agregar verbosidad con -t
y validación con -p
Respondido el 07 de junio de 20 a las 16:06
1
Esta podría ser la respuesta más simple y tal vez no funcione en todos los casos, pero funciona muy bien para mí:
while read line;do echo "$line";done<peptides.txt
si necesita encerrar entre paréntesis para espacios:
while read line;do echo \"$line\";done<peptides.txt
Ahhh, esta es más o menos la misma que la respuesta que recibió más votos, pero todo está en una sola línea.
Respondido el 11 de enero de 21 a las 04:01
0
Esto llega bastante tarde, pero con la idea de que puede ayudar a alguien, agrego la respuesta. Además, esta puede no ser la mejor manera. head
El comando se puede usar con -n
argumento para leer n líneas desde el inicio del archivo e igualmente tail
El comando se puede utilizar para leer desde abajo. Ahora, para ir a buscar nth línea de archivo, nos dirigimos n líneas, canalice los datos para que sigan solo una línea de los datos canalizados.
TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
echo $TOTAL_LINES # To validate total lines in the file
for (( i=1 ; i <= $TOTAL_LINES; i++ ))
do
LINE=`head -n$i $USER_FILE | tail -n1`
echo $LINE
done
Respondido 08 ago 19, 10:08
No hagas esto. Recorrer los números de línea y obtener cada línea individual a través de sed
or head
+ tail
is increíblemente ineficiente y, por supuesto, plantea la pregunta de por qué no utiliza simplemente una de las otras soluciones aquí. Si necesita saber el número de línea, agregue un contador a su while read -r
bucle, o use nl -ba
para agregar un prefijo de número de línea a cada línea antes del bucle. - triples
-1
@ Peter: Esto podría funcionar para ti-
echo "Start!";for p in $(cat ./pep); do
echo $p
done
Esto devolvería la salida
Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
Respondido 30 ago 15, 06:08
¡Esto es muy malo! Por qué no lee líneas con "para". - fedorqui 'ASÍ que deja de hacer daño'
¡Esta respuesta está derrotando todos los principios establecidos por las buenas respuestas anteriores! - códigoforester
Elimina esta respuesta. - dawg
Ahora chicos, no exageren. La respuesta es mala, pero parece funcionar, al menos para casos de uso simples. Siempre que se proporcione, ser una mala respuesta no le quita el derecho a existir de la respuesta. - egor hans
@EgorHans, no estoy de acuerdo: el objetivo de las respuestas es enseñar a la gente a escribir software. Enseñar a la gente a hacer las cosas de una manera que usted sabes qué es perjudicial para ellos y las personas que usan su software (introduciendo errores / comportamientos inesperados / etc.) está dañando a otros a sabiendas. Una respuesta que se sabe que es dañina no tiene "derecho a existir" en un recurso de enseñanza bien curado (y curarlo es exactamente lo que se supone que nosotros, las personas que estamos votando y marcando, debemos hacer aquí). - Carlos Duffy
Oh, veo que han sucedido muchas cosas aquí: se eliminaron todos los comentarios y se reabrió la pregunta. Solo como referencia, la respuesta aceptada en Leer un archivo línea por línea asignando el valor a una variable aborda el problema de manera canónica y debe preferirse sobre el aceptado aquí. - fedorqui 'SO stop harming'
bioinformática en bash 😁 - Kukuster