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
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).
cat peptides.txt | while read line
do
# do something with $line here
done
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
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!
Alcune altre cose non coperte da altre risposte:
# ':' 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 ...)
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 .
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?
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
.
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
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
Ulteriori informazioni sui comandi incorporati della shell read
e readarray
- GNU
Post correlati:
Usa un ciclo while, come questo:
while IFS= read -r line; do
echo "$line"
done <file
Gli appunti:
Se non si imposta correttamente IFS
, si perderanno i rientri.
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:
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
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.
#!/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
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.
@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