Pregunta Looping a través del contenido de un archivo en Bash


¿Cómo puedo iterar a través de cada línea de un archivo de texto con Intento?

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 tarde quiero hacer algo más complicado con $p que acaba de salir a la pantalla.)


La variable de entorno CÁSCARA es (desde 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

937
2017-10-05 17:52


origen


Respuestas:


Una forma de hacerlo es:

while read p; do
  echo $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).


1514
2017-10-05 18:00



cat peptides.txt | while read line
do
   # do something with $line here
done

293
2017-10-05 17:54



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 desde un descriptor de archivo (en este caso, descriptor de archivo n. ° 4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

Opcion 2: Para bucle: leer el archivo en una sola variable y analizar.
Esta sintaxis analizará "líneas" en función de cualquier espacio en blanco entre los tokens. Esto todavía funciona porque las líneas del archivo de entrada son tokens de una sola palabra. 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

107
2017-10-05 18:18



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 ideas únicas para profundizar 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 sola línea de comando. Cambia la parte "echo $ word" a lo que quieras y puedes emitir varios comandos separados por punto y coma. El siguiente ejemplo usa los contenidos 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 (learn sed) puede volcar el resultado 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 está escrito arriba porque he usado archivos de texto donde los he creado con una palabra por línea. (Ver comentarios) Si tiene espacios que no desea dividir sus palabras / líneas, se vuelve un poco más feo, pero el mismo comando 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. En este punto, es posible que desee considerar poner todo en un script de shell en lugar de exprimirlo todo en una sola línea.

¡La mejor de las suertes!


60
2017-10-04 13:30



Use un ciclo while, como este:

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

Notas:

  1. Si no configuras el IFS apropiadamente, perderás sangría.

  2. Casi siempre debes usar la opción -r con lectura.

  3. No lea líneas con for


36
2018-06-09 15:09



Algunas cosas más que no están cubiertas por otras respuestas:

Lectura 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

Lectura desde 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 bucle while se ejecuta en el shell actual en lugar de en una subcaja como en el caso de este último. Ver la publicación relacionada Una variable modificada dentro de un ciclo while no se recuerda.

Lectura desde una entrada delimitada nula, 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 los archivos que contienen líneas nuevas, espacios o ambos?

Lectura 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

Residencia en @ chepner's responder aquí:

-u es una extensión bash Para la compatibilidad POSIX, cada llamada se vería como read -r X <&3.

Lectura de un archivo completo en una matriz (versiones Bash anteriores a 4)

while read -r line; do
    my_array+=("$line")
done < my_file

Si el archivo finaliza 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

Lectura de un archivo completo en una matriz (Bash versiones 4x y posteriores)

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

Artículos Relacionados:


27
2018-01-14 03:30



Si no desea que su lectura se rompa por el carácter de nueva línea, use -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

A continuación, ejecute el script con el nombre del archivo como parámetro.


8
2018-03-08 16:10



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 del resultado de archivo leído por muchas soluciones de Bash:

  1. La línea en blanco 4;
  2. Espacios iniciales o finales en dos líneas;
  3. Mantener el significado de líneas individuales (es decir, cada línea es un registro);
  4. La línea 6 no termina 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 ciclo 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) Usando la sustitución del proceso con cat leerá el archivo completo de un trago y perderá 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 elimina el " de $(cat /tmp/test.txt) lees el archivo palabra por palabra en lugar de un trago. Probablemente tampoco lo que se pretende ...)


La forma más robusta y sencilla de leer un archivo línea por línea y conservar todos los espacios 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 despojar a los espacios líderes y comerciales, elimine 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 bajo POSIX. Si puede contar con el seguimiento \n usted no necesita || [[ -n $line ]] en el while lazo.)

Más en el Preguntas frecuentes sobre BASH


6
2018-02-03 19:15



#!/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

4
2017-11-14 14:23



Aquí está mi ejemplo de la vida real de cómo hacer un bucle en las líneas de otro resultado del programa, verificar las subcadenas, soltar las comillas dobles de la variable, usar esa variable fuera del ciclo. Supongo que muchos 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 ciclo, establecer el valor y usarlo fuera del ciclo requiere hecho <<< "$ (...)" sintaxis. La aplicación debe ejecutarse dentro de un contexto de consola actual. Las comillas alrededor del comando mantienen nuevas líneas de flujo de salida.

La coincidencia de bucle para subcadenas luego se lee nombre = valor par, divide la parte del lado derecho de la última = personaje, descarta la primera cotización, descarta la última cotización, tenemos un valor limpio para usar en otro lugar.


2
2018-06-30 08:15



@ Peter: Esto podría funcionar para usted-

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

1
2017-08-30 05:00