Angenommen, ich habe diese Datei:
hello world hello world
Dieses Programm
#!/bin/bash for i in $(cat $1); do echo "tester: $i" done
Ausgänge
tester: hello tester: world tester: hello tester: world
Ich hätte gerne die for
iteriere über jede Zeile einzeln und ignoriere Leerzeichen, dh die letzten beiden Zeilen sollten durch
tester: hello world
unter Verwendung von Anführungszeichen for i in "$(cat $1)";
führt dazu, dass i
die gesamte Datei auf einmal zugewiesen wird. Was muss ich ändern?
Antwort
(9 Jahre später 🙂
Beide bereitgestellten Antworten würden bei Dateien ohne Zeilenumbruch am Ende fehlschlagen. Dies überspringt effektiv die letzte Zeile, erzeugt keine Fehler und führt zu einer Katastrophe (hart erlernt) way :).
Die beste prägnante Lösung, die ich bisher gefunden habe, dass „Just Works“ (sowohl in bash als auch in sh):
while IFS="" read -r LINE || [ -n "${LINE}" ]; do echo "processing line: ${LINE}" done < /path/to/input/file.txt
Weitere Informationen finden Sie in dieser StackOverflow-Diskussion: Verwendung „while read „(Bash), um die letzte Zeile in einer Datei zu lesen, wenn am Ende der Datei keine neue Zeile steht?
Achtung: Dieser Ansatz fügt der letzten Zeile eine zusätzliche neue Zeile hinzu, falls vorhanden noch keine.
Kommentare
- Netter Fang, danke! Beachten Sie die " Beachten Sie, dass dies eine zusätzliche neue Zeile bei EOF hinzufügt, falls noch keine vorhanden ist. " Kommentar obwohl
- Tobias, ich ' füge dies als Notiz hinzu, danke.
Antwort
Mit for
und IFS :
#!/bin/bash IFS=$"\n" # make newlines the only separator set -f # disable globbing for i in $(cat < "$1"); do echo "tester: $i" done
Beachten Sie jedoch, dass leere Zeilen übersprungen werden, da newline ein IFS-Leerzeichen ist davon zählen als 1 und die führenden und nachfolgenden werden ignoriert. Mit zsh
und ksh93
(nicht bash
) können Sie es in IFS=$"\n\n"
für Zeilenumbrüche, die nicht speziell behandelt werden sollen. Beachten Sie jedoch, dass alle nachgestellten Zeilenumbrüche (einschließlich nachfolgender Leerzeilen) immer durch die Befehlsersetzung entfernt werden.
Oder mit read
(nicht mehr cat
):
#!/bin/bash while IFS= read -r line; do echo "tester: $line" done < "$1"
Dort bleiben leere Zeilen erhalten. Beachten Sie jedoch, dass die letzte Zeile übersprungen wird, wenn sie nicht ordnungsgemäß durch ein Zeilenumbruchzeichen begrenzt wird.
Kommentare
- Danke, ich wusste nicht, dass man
<
Ich sehe IFS \ read -r line' in second example. Is really
IFS = `benötigt? IMHO reicht es zu sagen:while read -r line; do echo "tester: $line"; done < "$1"
- @GrzegorzWierzowiecki
IFS=
deaktiviert das Entfernen von führenden und nachfolgenden Leerzeichen. Siehe Warum hat IFS inwhile IFS= read..
keine Auswirkung? - @BenMares Um ein Globbing zu verhindern Ausdrücke, die möglicherweise in dem Text erscheinen, den wir gerade lesen, von der Erweiterung auf übereinstimmende Dateinamen. Versuchen Sie beispielsweise
printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done
. - Ein
while IFS= read -r line || [ "$line" ]; do
verarbeitet eine nachfolgende Zeile, die nicht richtig durch ein Zeilenumbruchzeichen begrenzt ist (aber es wird wieder hinzugefügt).
Antwort
Für das, was es wert ist, muss ich tun das ziemlich oft und kann mich nie an die genaue Art der Verwendung von while IFS= read...
erinnern, also habe ich die folgende Funktion in meinem Bash-Profil definiert:
# iterate the line of a file and call input function iterlines() { (( $# < 2 )) && { echo "Usage: iterlines <File> <Callback>"; return; } local File=$1 local Func=$2 n=$(cat "$File" | wc -l) for (( i=1; i<=n; i++ )); do "$Func" "$(sed "${i}q;d" "$File")" done }
Diese Funktion bestimmt zuerst die Anzahl der Zeilen in der Datei, verwendet dann sed
, um Zeile für Zeile zu extrahieren, und übergibt jede Zeile als einzelnes Zeichenfolgenargument an eine bestimmte Zeichenfolge Funktion. Ich nehme an, dass dies bei großen Dateien sehr ineffizient sein könnte, aber das war bisher kein Problem für mich (Vorschläge zur Verbesserung dieses Willkommens natürlich).
Die Verwendung ist ziemlich süß IMO:
>> cat example.txt # note the use of spaces, whitespace, etc. a/path This is a sentence. "wi\th quotes" $End >> iterlines example.txt echo # preserves quotes, $ and whitespace a/path This is a sentence. "wi\th quotes" $End >> x() { echo "$#"; }; iterlines example.txt x # line always passed as single input string 1 1 1 1 1