Lesen Sie eine Datei Zeile für Zeile und weisen Sie den Wert einer Variablen zu

753

Ich habe die folgende TXT-Datei:

Marco
Paolo
Antonio

Ich möchte es Zeile für Zeile lesen und für jede Zeile möchte ich einer Variablen einen TXT-Zeilenwert zuweisen. Angenommen, meine Variable ist $name, der Fluss ist:

  • Lesen Sie die erste Zeile aus der Datei
  • Assign $name= "Marco"
  • Machen Sie einige Aufgaben mit $name
  • Lesen Sie die zweite Zeile aus der Datei
  • Assign $name= "Paolo"
Marco
quelle
2
Mögliches Duplikat von Durchlaufen des Inhalts einer Datei in Bash?
Andand
3
Können diese Fragen vielleicht irgendwie zusammengeführt werden? Beide haben einige wirklich gute Antworten, die verschiedene Aspekte des Problems hervorheben, die schlechten Antworten enthalten ausführliche Erklärungen in den Kommentaren, was an ihnen schlecht ist, und ab sofort können Sie aus den Antworten von nicht wirklich einen vollständigen Überblick darüber gewinnen, was zu beachten ist eine einzige Frage des Paares. Es wäre hilfreich, alles an einem Ort zu haben, anstatt es auf zwei Seiten zu verteilen.
Egor Hans

Antworten:

1357

Im Folgenden wird eine Datei gelesen, die zeilenweise als Argument übergeben wird:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Dies ist das Standardformular zum Lesen von Zeilen aus einer Datei in einer Schleife. Erläuterung:

  • IFS=(oder IFS='') verhindert, dass führende / nachfolgende Leerzeichen abgeschnitten werden.
  • -r verhindert, dass Backslash-Escapezeichen interpretiert werden.

Oder Sie können es in ein Bash-Datei-Hilfsskript einfügen, Beispielinhalt:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Wenn das oben Gesagte in einem Skript mit Dateiname gespeichert ist readfile, kann es wie folgt ausgeführt werden:

chmod +x readfile
./readfile filename.txt

Wenn die Datei keine Standard-POSIX-Textdatei ist (= nicht durch ein Zeilenumbruchzeichen abgeschlossen), kann die Schleife so geändert werden, dass nachfolgende Teilzeilen verarbeitet werden:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

|| [[ -n $line ]]Verhindert hier, dass die letzte Zeile ignoriert wird, wenn sie nicht mit a endet \n(da readein Exit-Code ungleich Null zurückgegeben wird, wenn EOF auftritt).

Wenn die Befehle in der Schleife auch aus der Standardeingabe gelesen werden, kann der von verwendete Dateideskriptor readauf etwas anderes geändert werden (vermeiden Sie die Standarddateideskriptoren ), z.

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Nicht-Bash-Muscheln wissen es möglicherweise nicht read -u3; verwenden Sie sie read <&3stattdessen.)

cppcoder
quelle
23
Bei dieser Methode gibt es eine Einschränkung. Wenn irgendetwas in der while-Schleife interaktiv ist (z. B. von stdin liest), wird die Eingabe von $ 1 übernommen. Sie haben keine Möglichkeit, Daten manuell einzugeben.
Carpie
10
Bemerkenswert - einige Befehle unterbrechen dies (wie in, sie unterbrechen die Schleife). Wenn Sie beispielsweise sshdas -nFlag nicht verwenden, können Sie die Schleife effektiv verlassen. Es gibt wahrscheinlich einen guten Grund dafür, aber es hat eine Weile gedauert, bis ich herausgefunden habe, warum mein Code fehlgeschlagen ist, bevor ich dies entdeckt habe.
Alex
6
als Einzeiler: während IFS = '' -r Zeile || lesen [[-n "$ line"]]; Echo "$ line"; erledigt <Dateiname
Joseph Johnson
8
@ OndraŽižka, das wird durch den ffmpegKonsum von stdin verursacht. Fügen Sie </dev/nullIhrer ffmpegZeile hinzu, und es wird nicht möglich sein oder eine alternative FD für die Schleife verwenden. Dieser "alternative FD" -Ansatz sieht so aus while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy
9
murren bezüglich : Beratung einer .shErweiterung. Ausführbare Dateien unter UNIX haben normalerweise überhaupt keine Erweiterungen (Sie werden nicht ausgeführt ls.elf), und ein Bash Shebang (und nur Bash-Tools wie [[ ]]) und eine Erweiterung, die POSIX sh-Kompatibilität impliziert, sind intern widersprüchlich.
Charles Duffy
309

Ich ermutige Sie, die -rFlagge zu verwenden, für readdie steht:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Ich zitiere aus man 1 read.

Eine andere Sache ist, einen Dateinamen als Argument zu nehmen.

Hier ist aktualisierter Code:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
Grzegorz Wierzowiecki
quelle
4
Trimmt führenden und nachlaufenden Raum von der Linie
Barfuin
@ Thomas und was passiert mit den Räumen in der Mitte? Hinweis: Unerwünschte versuchte Befehlsausführung.
kmarsh
1
Dies funktionierte bei mir im Gegensatz zur akzeptierten Antwort.
Neurotransmitter
3
@TranslucentCloud, wenn dies funktioniert hat und die akzeptierte Antwort nicht, vermute ich, dass Ihre Shell shnicht war bash; Der erweiterte || [[ -n "$line" ]]Testbefehl, der in der Syntax der akzeptierten Antwort verwendet wird, ist ein Bashismus. Diese Syntax hat jedoch tatsächlich eine relevante Bedeutung: Sie bewirkt, dass die Schleife für die letzte Zeile in der Eingabedatei fortgesetzt wird, auch wenn sie keine neue Zeile enthält. Wenn Sie dies auf POSIX-kompatible Weise tun möchten || [ -n "$line" ], möchten Sie lieber verwenden [als [[.
Charles Duffy
3
Dies muss jedoch noch geändert werden, IFS=um das readTrimmen von Leerzeichen zu verhindern.
Charles Duffy
132

Die Verwendung der folgenden Bash-Vorlage sollte es Ihnen ermöglichen, jeweils einen Wert aus einer Datei zu lesen und zu verarbeiten.

while read name; do
    # Do what you want to $name
done < filename
OneWinged
quelle
14
als Einzeiler: beim Lesen des Namens; echo $ {name}; erledigt <Dateiname
Joseph Johnson
4
@CalculusKnight, es hat nur "funktioniert", weil Sie nicht genügend interessante Daten zum Testen verwendet haben. Versuchen Sie es mit Inhalten mit umgekehrten Schrägstrichen oder mit einer Zeile, die nur enthält *.
Charles Duffy
7
@Matthias, Annahmen, die sich letztendlich als falsch herausstellen, sind eine der größten Fehlerquellen, sowohl sicherheitsrelevant als auch anderweitig. Das größte Datenverlustereignis, das ich jemals gesehen habe, war auf ein Szenario zurückzuführen, von dem jemand angenommen hatte, dass es "buchstäblich nie auftauchen" würde - ein Pufferüberlauf, der zufälligen Speicher in einen Puffer zum Benennen von Dateien speichert und ein Skript verursacht, das Annahmen darüber macht, welche Namen möglicherweise jemals auftreten könnten auftreten, sehr, sehr unglückliches Verhalten zu haben.
Charles Duffy
5
@Matthias, ... und das ist vor allem hier wahr, da Codebeispiele bei Stackoverflow dargestellt sind als Lehrmittel verwendet werden, für Leute , die Muster in ihrer eigenen Arbeit wieder zu verwenden!
Charles Duffy
5
@Matthias, ich bin völlig anderer Meinung als die Behauptung, dass "Sie Ihren Code nur für Daten entwickeln sollten, die Sie erwarten". In unerwarteten Fällen befinden sich Ihre Fehler, Ihre Sicherheitslücken - deren Behandlung ist der Unterschied zwischen Slapdash-Code und robustem Code. Zugegeben, diese Behandlung muss nicht ausgefallen sein - sie kann nur "mit einem Fehler beenden" sein -, aber wenn Sie überhaupt keine Behandlung haben, ist Ihr Verhalten in unerwarteten Fällen undefiniert.
Charles Duffy
76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
Gert
quelle
8
Nichts gegen die anderen Antworten, vielleicht sind sie etwas ausgefeilter, aber ich stimme dieser Antwort zu, weil sie einfach, lesbar und für das, was ich brauche, ausreicht. Beachten Sie, dass die zu lesende Textdatei mit einer Leerzeile enden muss (dh Enternach der letzten Zeile gedrückt werden muss), damit sie funktioniert. Andernfalls wird die letzte Zeile ignoriert. Zumindest ist mir das passiert.
Antonio Vinicius Menezes Medei
12
Nutzloser Gebrauch von Katze, sicher?
Brian Agnew
5
Und das Zitat ist gebrochen; und Sie sollten keine Variablennamen in Großbuchstaben verwenden, da diese für die Systemnutzung reserviert sind.
Tripleee
7
@AntonioViniciusMenezesMedei, ... außerdem habe ich gesehen, dass Leute finanzielle Verluste erlitten haben, weil sie angenommen haben, dass diese Vorbehalte für sie niemals von Bedeutung sein würden; konnte keine guten Praktiken lernen; und folgte dann den Gewohnheiten, die sie beim Schreiben von Skripten gewohnt waren, mit denen Sicherungen kritischer Rechnungsdaten verwaltet wurden. Es ist wichtig zu lernen, die Dinge richtig zu machen.
Charles Duffy
6
Ein weiteres Problem besteht darin, dass die Pipe eine neue Unterschale öffnet, dh alle in der Schleife festgelegten Variablen können nach Beendigung der Schleife nicht gelesen werden.
mxmlnkn
20

Viele Leute haben eine Lösung veröffentlicht, die überoptimiert ist. Ich denke nicht, dass es falsch ist, aber ich denke demütig, dass eine weniger optimierte Lösung wünschenswert sein wird, damit jeder leicht verstehen kann, wie dies funktioniert. Hier ist mein Vorschlag:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"
Raul Luna
quelle
20

Verwenden:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Wenn Sie IFSanders eingestellt haben, erhalten Sie ungerade Ergebnisse.

user3546841
quelle
34
Dies ist eine schreckliche Methode . Bitte verwenden Sie es nicht, es sei denn, Sie möchten Probleme mit dem Globbing haben, die auftreten, bevor Sie es bemerken!
gniourf_gniourf
13
@MUYBelgium hast du es mit einer Datei versucht, die eine einzelne *in einer Zeile enthält? Jedenfalls ist dies ein Antimuster . Lies keine Zeilen mit für .
gniourf_gniourf
2
@ OndraŽižka, der readAnsatz ist der Best-Practice-Ansatz im Konsens der Gemeinschaft . Die Einschränkung, die Sie in Ihrem Kommentar erwähnen, gilt, wenn Ihre Schleife Befehle (z. B. ffmpeg) ausführt, die aus stdin gelesen werden. Dies wird trivial gelöst, indem ein Nicht-stdin-FD für die Schleife verwendet oder die Eingabe solcher Befehle umgeleitet wird. Im Gegensatz fordazu bedeutet das Umgehen des Globbing- Fehlers in Ihrem -loop-Ansatz, dass Änderungen an den Shell-globalen Einstellungen vorgenommen werden (und dann rückgängig gemacht werden müssen).
Charles Duffy
1
@ OndraŽižka, ... außerdem bedeutet der hier verwendete forSchleifenansatz, dass der gesamte Inhalt eingelesen werden muss, bevor die Schleife überhaupt ausgeführt werden kann. Dies macht ihn völlig unbrauchbar, wenn Sie Gigabyte an Daten durchlaufen, selbst wenn Sie diese deaktiviert haben Globbing; Die while readSchleife muss nicht mehr als die Daten einer einzelnen Zeile gleichzeitig speichern. Dies bedeutet, dass sie ausgeführt werden kann, während der Inhalt zur Erzeugung des Unterprozesses noch ausgeführt wird (und somit für Streaming-Zwecke verwendet werden kann). Außerdem ist der Speicherverbrauch begrenzt.
Charles Duffy
1
Tatsächlich whilescheinen selbstbasierte Ansätze Probleme mit * -Zeichen zu haben. Siehe Kommentare der akzeptierten Antwort oben. Nicht dagegen zu argumentieren, dass Dateien ein Antimuster sind.
Egor Hans
9

Wenn Sie sowohl die Eingabedatei als auch die Benutzereingabe (oder etwas anderes von stdin) verarbeiten müssen, verwenden Sie die folgende Lösung:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

Basierend auf der akzeptierten Antwort und dem Tutorial zur Weiterleitung von Bash-Hackern .

Hier öffnen wir den Dateideskriptor 3 für die als Skriptargument übergebene Datei und weisen readan, diesen Deskriptor als input ( -u 3) zu verwenden. Daher belassen wir den Standardeingabedeskriptor (0) an einem Terminal oder einer anderen Eingabequelle, der Benutzereingaben lesen kann.

gluk47
quelle
7

Für eine ordnungsgemäße Fehlerbehandlung:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
bviktor
quelle
Könnten Sie bitte eine Erklärung hinzufügen?
Tyler Christian
Leider fehlt die letzte Zeile in der Datei.
Ungalcrys
... und aufgrund des fehlenden Zitierens auch Munges-Zeilen, die Platzhalter enthalten - wie in BashPitfalls # 14 beschrieben .
Charles Duffy
0

Im Folgenden wird nur der Inhalt der Datei ausgedruckt:

cat $Path/FileName.txt

while read line;
do
echo $line     
done
Rekha Ghanate
quelle
1
Diese Antwort fügt wirklich nichts über vorhandene Antworten hinzu, funktioniert aufgrund eines Tippfehlers / Fehlers nicht und bricht in vielerlei Hinsicht ab.
Konrad Rudolph
0

Verwenden Sie das IFS-Tool (Internal Field Separator) in Bash. Definiert das Zeichen, mit dem Zeilen in Token getrennt werden. Standardmäßig enthält es < tab > / < Leerzeichen > / < newLine >

Schritt 1 : Laden Sie die Dateidaten und fügen Sie sie in die Liste ein:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

Schritt 2 : Jetzt iterieren und drucken Sie die Ausgabe:

for line in "${array[@]}"
  do
    echo "$line"
  done

Echospezifischer Index im Array : Zugriff auf eine Variable im Array:

echo "${array[0]}"
avivamg
quelle