¿Cómo itero a través de cada línea de un archivo de texto con Bash ?
Con este script:
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 adelante, quiero hacer algo más complicado con $p
que simplemente mostrar en la pantalla).
La variable de entorno Shell is (de env):
Shell=/bin/bash
Salida de /bin/bash --version
:
GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
Salida de cat /proc/version
:
Linux version 2.6.18.2-34-default ([email protected]) (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
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 diagonal inversa y omitir la línea final si falta una alimentación de línea de terminación. Si estas son preocupaciones, puedes hacer:
while IFS="" read -r p || [ -n "$p" ]
do
printf '%s\n' "$p"
done < peptides.txt
Excepcionalmente, si el cuerpo del bucle puede leer la entrada estándar , puede abrir el archivo utilizando 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).
cat peptides.txt | while read line
do
# do something with $line here
done
Opción 1a: While loop: 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: While loop: Línea única a la vez:
Abra el archivo, lea de 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
Opción 2: For loop: lee el archivo en una sola variable y analiza.
Esta sintaxis analizará "líneas" basadas en cualquier espacio en blanco entre las fichas. Esto todavía funciona porque las líneas del archivo de entrada dado son tokens de Word único. Si hubiera más de un token por línea, este método no funcionaría. Además, leer el archivo completo en una sola variable no es una buena estrategia para archivos grandes.
#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
echo $line
done
Esto 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 archivos de una sola línea para excavar en listas de 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 comando. Cambie la parte de "echo $ Word" a lo que desee y puede 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 pretende usar esto como un editor de secuencias (learn 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
He usado estos como están escritos 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 pone un poco más feo, pero el mismo comando sigue funcionando 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 simplemente le dice al Shell que se divida solo en las nuevas líneas, no en los espacios, y luego devuelve el entorno a lo que era anteriormente. En este punto, es posible que desee considerar poner todo en un script de Shell en lugar de apretarlo todo en una sola línea, sin embargo.
¡La mejor de las suertes!
Unas cuantas cosas más no cubiertas por otras respuestas:
# ':' 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
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 una subshell como en el caso de este último. Consulte la publicación relacionada Una variable modificada dentro de un bucle while no se recuerda .
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 con seguridad nombres de archivos que contengan líneas nuevas, espacios o ambos?
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 answer aquí :
-u
es una extensión de bash. Para la compatibilidad con POSIX, cada llamada se vería como read -r X <&3
.
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
readarray -t my_array < my_file
o
mapfile -t my_array < my_file
Y entonces
for line in "${my_array[@]}"; do
# process the lines
done
Más acerca de los comandos read
y readarray
incorporados en el shell - GNU
Artículos Relacionados:
Usa un bucle while, como este:
while IFS= read -r line; do
echo "$line"
done <file
Notas:
Si no establece la IFS
correctamente, perderá la sangría.
Si no desea que su lectura sea interrumpida por el 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.
Supongamos 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 que leen muchas soluciones Bash:
Si desea el archivo de texto línea por línea, incluidas las líneas en blanco y las 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
devuelve):
1) Perder la última línea y 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 hace 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 así pierde la última línea si no se termina con CR)
2) El uso de la sustitución de procesos con cat
leerá todo el archivo de una sola vez y perderá el significado de 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 elimina el "
de $(cat /tmp/test.txt)
, lea el archivo palabra por palabra en lugar de un trago. Además, probablemente no sea lo que se pretende ...)
La forma más robusta 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 la parte IFS=
:
$ 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 un \n
de terminación, aunque bastante común, se considera roto en POSIX. Si puede contar con el \n
final, no necesita || [[ -n $line ]]
en el bucle while
).
Más en el BASH FAQ
#!/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
Aquí está mi ejemplo de la vida real: cómo hacer un bucle en las líneas de otra salida del programa, buscar subcadenas, eliminar comillas dobles de la variable, usar esa variable fuera del bucle. Supongo que muchos están haciendo 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 utilizarlo fuera del bucle requiere hecho <<< "$ (...)" sintaxis. La aplicación debe ejecutarse dentro de un contexto de la consola actual. Las citas alrededor del comando mantienen nuevas líneas de flujo de salida.
La concordancia de bucle para las subcadenas luego lee nombre = valor par, divide la parte del lado derecho del último = carácter, deja caer la primera cita, elimina la última cita, tenemos un valor limpio para ser usado en otros lugares.
@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