Frage Durchsuchen des Inhalts einer Datei in Bash


Wie durchlaufe ich jede Zeile einer Textdatei mit Bash?

Mit diesem Skript:

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

Ich bekomme diese Ausgabe auf dem Bildschirm:

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

(Später möchte ich etwas komplizierter machen mit $p als nur auf dem Bildschirm ausgeben.)


Die Umgebungsvariable SCHALE ist (von env):

SHELL=/bin/bash

/bin/bash --version Ausgabe:

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

cat /proc/version Ausgabe:

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

Die Datei peptides.txt enthält:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL

937
2017-10-05 17:52


Ursprung


Antworten:


Eine Möglichkeit, es zu tun ist:

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

Ausnahmsweise, wenn die Schleifenkörper kann von der Standardeingabe lesen, Sie können die Datei mit einem anderen Dateideskriptor öffnen:

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

Hier ist 10 nur eine willkürliche Zahl (anders als 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



Option 1a: While-Schleife: jeweils eine Zeile: Eingabeumleitung

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

Option 1b: While-Schleife: Eine Zeile gleichzeitig:
Öffnen Sie die Datei, lesen Sie von einem Dateideskriptor (in diesem Fall Dateideskriptor # 4).

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

Option 2: For-Schleife: Datei in einzelne Variable lesen und analysieren.
Diese Syntax analysiert "Zeilen" basierend auf einem Leerraum zwischen den Token. Dies funktioniert immer noch, weil die angegebenen Eingabedateizeilen Einzelwort-Token sind. Wenn mehr als ein Token pro Zeile vorhanden wäre, würde diese Methode nicht funktionieren. Außerdem ist das Lesen der vollständigen Datei in eine einzelne Variable keine gute Strategie für große Dateien.

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

107
2017-10-05 18:18



Dies ist nicht besser als andere Antworten, aber es ist eine weitere Möglichkeit, die Arbeit in einer Datei ohne Leerzeichen zu erledigen (siehe Kommentare). Ich finde, dass ich oft Einzeiler brauche, um Listen in Textdateien zu durchforsten, ohne den zusätzlichen Schritt, separate Skriptdateien zu verwenden.

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

Dieses Format ermöglicht es mir, alles in eine Befehlszeile zu bringen. Ändern Sie den "echo $ word" -Teil auf was auch immer Sie wollen und Sie können mehrere Befehle ausgeben, die durch Semikolons getrennt sind. Im folgenden Beispiel werden die Inhalte der Datei als Argumente für zwei andere Skripts verwendet, die Sie möglicherweise geschrieben haben.

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

Oder wenn Sie dies wie einen Stream-Editor verwenden möchten (learn sed), können Sie die Ausgabe wie folgt in eine andere Datei ausgeben.

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

Ich habe diese wie oben beschrieben verwendet, weil ich Textdateien verwendet habe, wo ich sie mit einem Wort pro Zeile erstellt habe. (Siehe Kommentare) Wenn Sie Leerzeichen haben, die Sie Ihre Wörter / Zeilen nicht teilen möchten, wird es ein wenig hässlicher, aber derselbe Befehl funktioniert immer noch wie folgt:

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

Dies teilt der Shell nur mit, dass sie nur auf Zeilenumbruch und nicht auf Leerzeichen aufgeteilt werden soll. Anschließend wird die Umgebung wieder auf die vorherige Position zurückgesetzt. An dieser Stelle sollten Sie in Betracht ziehen, alles in ein Shell-Skript zu schreiben, anstatt alles in eine einzige Zeile zu komprimieren.

Viel Glück!


60
2017-10-04 13:30



Verwenden Sie eine while-Schleife wie folgt:

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

Anmerkungen:

  1. Wenn Sie das nicht einstellen IFS richtig, Sie werden den Eindruck verlieren.

  2. Sie sollten fast immer die Option -r beim Lesen verwenden.

  3. Lies keine Zeilen mit for


36
2018-06-09 15:09



Ein paar weitere Dinge, die nicht durch andere Antworten abgedeckt sind:

Lesen aus einer Datei mit Trennzeichen

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

Lesen von der Ausgabe eines anderen Befehls unter Verwendung der Prozesssubstitution

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

Dieser Ansatz ist besser als command ... | while read -r line; do ... weil die while-Schleife hier in der aktuellen Shell statt einer Subshell wie im Fall der letzteren läuft. Siehe den zugehörigen Beitrag Eine in einer While-Schleife geänderte Variable wird nicht gespeichert.

B. von einer Null-Eingabe lesen 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)

Verwandte lesen: BashFAQ / 020 - Wie kann ich Dateinamen mit Zeilenumbrüchen, Leerzeichen oder beidem finden und sicher verarbeiten?

Lesen von mehr als einer Datei gleichzeitig

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

Beyogen auf @ chepners Antworten Hier:

-u ist eine Bash-Erweiterung. Für die POSIX-Kompatibilität würde jeder Aufruf ungefähr so ​​aussehen read -r X <&3.

Lesen einer ganzen Datei in ein Array (Bash-Versionen früher bis 4)

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

Wenn die Datei mit einer unvollständigen Zeile endet (am Ende fehlt eine neue Zeile), dann:

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

Lesen einer ganzen Datei in ein Array (Bash-Versionen 4x und höher)

readarray -t my_array < my_file

oder

mapfile -t my_array < my_file

Und dann

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

Zusammenhängende Posts:


27
2018-01-14 03:30



Wenn Sie nicht möchten, dass Ihr Lesevorgang durch einen Zeilenumbruch unterbrochen wird, verwenden Sie -

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

Führen Sie dann das Skript mit dem Dateinamen als Parameter aus.


8
2018-03-08 16:10



Angenommen, Sie haben diese Datei:

$ 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

Es gibt vier Elemente, die die Bedeutung der von vielen Bash-Lösungen gelesenen Dateiausgabe ändern werden:

  1. Die Leerzeile 4;
  2. Führende oder nachfolgende Leerzeichen in zwei Zeilen;
  3. Die Bedeutung einzelner Zeilen beibehalten (d. H. Jede Zeile ist eine Aufzeichnung);
  4. Die Linie 6 wurde nicht mit einem CR abgeschlossen.

Wenn Sie die Textdatei zeilenweise einschließlich Leerzeilen und Abschlusszeilen ohne CR möchten, müssen Sie eine While-Schleife verwenden und Sie müssen einen alternativen Test für die letzte Zeile haben.

Hier sind die Methoden, die die Datei ändern können (im Vergleich zu was cat kehrt zurück):

1) Verliere die letzte Zeile und führende und nachfolgende Leerzeichen:

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

(Wenn Sie tun while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt Stattdessen behalten Sie die führenden und nachgestellten Leerzeichen bei, verlieren aber immer noch die letzte Zeile, wenn sie nicht mit CR beendet wird.

2) Verwenden der Prozesssubstitution mit cat Will liest die gesamte Datei in einem Zug und verliert die Bedeutung einzelner Zeilen:

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

(Wenn Sie das entfernen " von $(cat /tmp/test.txt) Sie lesen die Datei Wort für Wort und nicht einen Schluck. Vermutlich auch nicht was gemeint ist ...)


Die robusteste und einfachste Art, eine Datei Zeile für Zeile zu lesen und alle Abstände zu erhalten, ist:

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

Wenn Sie führende und Handelsräume entfernen möchten, entfernen Sie die IFS= Teil:

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

(Eine Textdatei ohne Beendigung \n, obwohl ziemlich häufig, gilt als unter POSIX gebrochen. Wenn Sie auf das Nachlaufende zählen können \n du brauchst nicht || [[ -n $line ]] in dem while Schleife.)

Mehr bei der BASH FAQ


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



Hier ist mein Beispiel aus dem wirklichen Leben, wie man Zeilen einer anderen Programmausgabe schleifen kann, nach Teilzeichenfolgen sucht, doppelte Anführungszeichen von Variablen löscht und diese Variable außerhalb der Schleife verwendet. Ich denke, viele fragen diese Fragen früher oder später.

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

Deklariere die Variable außerhalb der Schleife, setze den Wert und verwende sie außerhalb der Schleife fertig <<< "$ (...)" Syntax. Die Anwendung muss in einem Kontext der aktuellen Konsole ausgeführt werden. Mit Anführungszeichen um den Befehl werden neue Zeilen des Ausgabestreams beibehalten.

Die Schleifenübereinstimmung für Teilstrings wird dann gelesen Name = Wert Paar, teilt den rechten Teil des Leistens = Zeichen, löscht erstes Zitat, letztes Zitat, wir haben einen sauberen Wert, der anderswo verwendet werden kann.


2
2018-06-30 08:15