risposta-alla-domanda-sullo-sviluppo-web-bd.com

Effettuare il looping del contenuto di un file in Bash

Come faccio a scorrere ogni riga di un file di testo con Bash ?

Con questo script:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

Ottengo questo risultato sullo schermo:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(Più avanti voglio fare qualcosa di più complicato con $p piuttosto che l'output sullo schermo.)


La variabile di ambiente Shell è (da env):

Shell=/bin/bash

/bin/bash --version output:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version output:

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

Il file peptides.txt contiene:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
1148
Peter Mortensen

Un modo per farlo è:

while read p; do
  echo "$p"
done <peptides.txt

Come sottolineato nei commenti, questo ha gli effetti collaterali del taglio degli spazi bianchi iniziali, dell'interpretazione delle sequenze di backslash e del salto della riga finale se manca un avanzamento riga finale. Se questi sono problemi, puoi fare:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

Eccezionalmente, se il corpo del ciclo può leggere dallo standard input , è possibile aprire il file utilizzando un descrittore di file diverso:

while read -u 10 p; do
  ...
done 10<peptides.txt

Qui, 10 è solo un numero arbitrario (diverso da 0, 1, 2).

1813
Bruno De Fraine
cat peptides.txt | while read line
do
   # do something with $line here
done
354
Warren Young

Opzione 1a: Ciclo while: Linea singola alla volta: reindirizzamento dell'input

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

Opzione 1b: Ciclo while: Linea singola alla volta:
Apri il file, leggi da un descrittore di file (in questo caso descrittore di file n. 4).

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

Opzione 2: Per ciclo: legge il file in una singola variabile e analizza.
Questa sintassi analizzerà le "linee" in base a qualsiasi spazio bianco tra i token. Funziona ancora perché le righe del file di input fornite sono token a parola singola. Se c'erano più di un token per riga, questo metodo non funzionerebbe. Inoltre, leggere il file completo in una singola variabile non è una buona strategia per i file di grandi dimensioni.

#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
131
Stan Graves

Questo non è migliore di altre risposte, ma è un altro modo per portare a termine il lavoro in un file senza spazi (vedi commenti). Trovo che spesso ho bisogno di one-liner per scavare negli elenchi in file di testo senza il passaggio extra di utilizzare file di script separati.

for Word in $(cat peptides.txt); do echo $Word; done

Questo formato mi consente di mettere tutto in una riga di comando. Cambia la parte "echo $ Word" in qualsiasi cosa desideri e puoi impartire comandi multipli separati da punto e virgola. L'esempio seguente utilizza i contenuti del file come argomenti in altri due script che potresti aver scritto.

for Word in $(cat peptides.txt); do cmd_a.sh $Word; cmd_b.py $Word; done

O se si intende utilizzare questo come un editor di stream (learn sed) è possibile scaricare l'output in un altro file come segue.

for Word in $(cat peptides.txt); do cmd_a.sh $Word; cmd_b.py $Word; done > outfile.txt

Ho usato questi come scritto sopra perché ho usato i file di testo in cui li ho creati con una parola per riga. (Vedi commenti) Se hai spazi che non vuoi dividere le tue parole/linee, diventa un po 'più brutto, ma lo stesso comando funziona ancora come segue:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

Questo dice semplicemente alla Shell di dividere solo su newline, non spazi, quindi restituisce l'ambiente a ciò che era in precedenza. A questo punto, potresti prendere in considerazione la possibilità di inserire tutto in uno script di Shell piuttosto che comprimerlo tutto in una singola riga, comunque.

Buona fortuna!

70
mightypile

Alcune altre cose non coperte da altre risposte:

Lettura da un file delimitato

# ':' 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

Lettura dall'output di un altro comando, usando la sostituzione del processo

while read -r line; do
  # process the line
done < <(command ...)

Questo approccio è migliore di command ... | while read -r line; do ... perché il ciclo while qui viene eseguito nella shell corrente anziché in una subshell come nel caso di quest'ultima. Vedi il post correlato Una variabile modificata all'interno di un ciclo while non viene ricordata .

Lettura da un input delimitato da null, ad esempio 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)

Related read: BashFAQ/020 - Come posso trovare e gestire in sicurezza i nomi dei file che contengono newline, spazi o entrambi?

Lettura da più di un file alla volta

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

Basato su @ chepner's answer qui :

-u è un'estensione bash. Per la compatibilità POSIX, ogni chiamata sarebbe simile a read -r X <&3.

Lettura di un intero file in un array (versioni di Bash precedenti alla 4)

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

Se il file termina con una riga incompleta (newline mancante alla fine), quindi:

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

Lettura di un intero file in un array (versioni di Bash 4x e successive)

readarray -t my_array < my_file

o

mapfile -t my_array < my_file

E poi

for line in "${my_array[@]}"; do
  # process the lines
done

Post correlati:

58
codeforester

Usa un ciclo while, come questo:

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

Gli appunti:

  1. Se non si imposta correttamente IFS, si perderanno i rientri.

  2. Si dovrebbe quasi sempre usare l'opzione -r con read.

  3. Non leggere le righe con for

42
Jahid

Supponiamo di avere questo file:

$ 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

Ci sono quattro elementi che alterano il significato dell'output del file letto da molte soluzioni Bash:

  1. La riga vuota 4;
  2. Spazi iniziali o finali su due linee;
  3. Mantenere il significato delle singole righe (ad esempio, ogni riga è un record);
  4. La linea 6 non è stata terminata con un CR.

Se si desidera il file di testo riga per riga, comprese le righe vuote e le linee di terminazione senza CR, è necessario utilizzare un ciclo while e si deve avere un test alternativo per la linea finale.

Ecco i metodi che possono modificare il file (rispetto a ciò che cat restituisce):

1) Perdere l'ultima riga e gli spazi iniziali e finali:

$ 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'

(Se invece si utilizza while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt, si conservano gli spazi iniziali e finali ma si perde comunque l'ultima riga se non viene terminata con CR)

2) L'uso della sostituzione di processo con cat legge l'intero file in un solo sorso e perde il significato delle singole righe:

$ 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'

(Se rimuovi " da $(cat /tmp/test.txt) leggi il file Word per parola invece di un gulp. Probabilmente non è ciò che è inteso ...)


Il modo più semplice e robusto per leggere un file riga per riga e conservare tutta la spaziatura è:

$ 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'

Se si desidera rimuovere spazi iniziali e commerciali, rimuovere 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 file di testo senza terminare \n, anche se abbastanza comune, è considerato non valido in POSIX. Se puoi contare sul file finale \n non hai bisogno di || [[ -n $line ]] nel ciclo while.)

Altro alle BASH FAQ

13
dawg

Se non vuoi che la tua lettura venga interrotta dal carattere di nuova riga, usa -

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

Quindi esegui lo script con il nome del file come parametro.

13
Anjul Sharma
#!/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
Sine

Ecco il mio esempio di vita reale su come eseguire il loop delle righe di un altro output del programma, controllare le sottostringhe, eliminare le virgolette doppie dalla variabile, utilizzare quella variabile al di fuori del ciclo. Immagino che molti stiano facendo queste domande prima o poi.

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

Dichiarare la variabile al di fuori del ciclo, impostare il valore e usarlo al di fuori del ciclo richiede fatto <<< "$ (...)" sintassi. L'applicazione deve essere eseguita all'interno di un contesto della console corrente. Le virgolette attorno al comando mantengono le nuove linee del flusso di output.

La corrispondenza del ciclo per sottostringhe poi legge nome = valore coppia, divide parte del lato destro dell'ultimo = carattere, gocce prima citazione, gocce ultima citazione, abbiamo un valore pulito da utilizzare altrove.

3
Whome

@Peter: potrebbe funzionare per te-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

Ciò restituirebbe l'output

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
0
Alan Jebakumar