Lesen Sie eine zeilenorientierte Datei, die möglicherweise nicht mit einer neuen Zeile endet

11

Ich habe eine Datei mit dem Namen, /tmp/urlFilein der jede Zeile eine URL darstellt. Ich versuche aus der Datei wie folgt zu lesen:

cat "/tmp/urlFile" | while read url
do
    echo $url
done

Wenn die letzte Zeile nicht mit einem Zeilenumbruchzeichen endet, wird diese Zeile nicht gelesen. Ich habe mich gefragt warum?

Ist es möglich, alle Zeilen zu lesen, unabhängig davon, ob sie mit einer neuen Zeile enden oder nicht?

Tim
quelle
2
Hah @ Stéphane Ich mag die TBD dort ;-).
Stephen Kitt
2
Eine andere Möglichkeit, die nachfolgende neue Zeile hinzuzufügen, wenn sie fehlt. awk 1 /tmp/urlFile.. soawk 1 /tmp/urlFile | while ...
Muru
@muru, das ist eine bessere Antwort als jede andere hier.
Wildcard
1
Da Sie fragen, warum es nicht gelesen wird: stackoverflow.com/a/729795/1968
Konrad Rudolph

Antworten:

13

Du würdest tun:

while IFS= read -r url || [ -n "$url" ]; do
  printf '%s\n' "$url"
done < url.list

(Diese Schleife fügt effektiv die fehlende neue Zeile in der letzten (Nicht-) Zeile zurück.)

Siehe auch:

Stéphane Chazelas
quelle
Vielen Dank. Ich habe die verlinkten Artikel gelesen und vermisse vielleicht etwas, warum "diese Schleife fügt die fehlende neue Zeile in der letzten (Nicht-) Zeile zurück"?
Tim
1
@Tim Was Stephane zu bedeuten scheint, ist, dass es die fehlende neue Zeile in der Ausgabe wieder hinzufügt, da alle printfAufrufe hier haben \n.
Sergiy Kolodyazhnyy
6

Dies scheint teilweise gelöst zu sein mit readarray -t:

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    printf '%s\n' "$url"
done

Beachten Sie jedoch, dass dies zwar für Dateien mit angemessener Größe funktioniert, diese Lösung jedoch ein potenziell neues Problem bei sehr großen Dateien mit sich bringt. Sie liest die Datei zunächst in ein Array, das dann durchlaufen werden muss. Bei sehr großen Dateien kann dies sowohl zeit- als auch speicherintensiv sein, möglicherweise bis zum Ausfall.

DopeGhoti
quelle
Vielen Dank. Welchen Teil löst es und welchen nicht?
Tim
Es löst das Problem mit dem Fehlen eines nachgestellten Zeilenumbruchs, führt jedoch bei sehr großen Dateien zu einem potenziellen neuen Problem, da die Datei zunächst in ein Array eingelesen wird, das dann durchlaufen werden muss.
DopeGhoti
1
@DopeGhoti Das sind gute Informationen - kann ich vorschlagen, dass Sie sie direkt in die Antwort einfügen?
RJHunter
Die Antwort wurde so geändert.
DopeGhoti
5

Per Definition besteht eine Textdatei aus einer Folge von Zeilen. Eine Zeile endet mit einem Zeilenumbruch. Daher endet eine Textdatei mit einem Zeilenumbruch, sofern sie nicht leer ist.

Das readeingebaute Gerät dient nur zum Lesen von Textdateien. Sie übergeben keine Textdatei und können daher nicht hoffen, dass sie nahtlos funktioniert. Die Shell liest alle Zeilen - was sie überspringt, sind die zusätzlichen Zeichen nach der letzten Zeile.

Wenn Sie eine möglicherweise fehlerhafte Eingabedatei haben, in der möglicherweise die letzte Zeile fehlt, können Sie zur Sicherheit eine neue Zeile hinzufügen.

{ cat "/tmp/urlFile"; echo; } | 

Dateien, bei denen es sich um Textdateien handeln sollte, denen jedoch der letzte Zeilenumbruch fehlt, werden häufig von Windows-Editoren erstellt. Dies erfolgt normalerweise in Kombination mit Windows-Zeilenenden, bei denen es sich im Gegensatz zu Unix LF um CR LF handelt. CR-Zeichen sind selten nützlich und können auf keinen Fall in URLs angezeigt werden. Sie sollten sie daher entfernen.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | 

Wenn die Eingabedatei wohlgeformt ist und mit einer neuen echoZeile endet, wird eine zusätzliche leere Zeile hinzugefügt . Da URLs nicht leer sein dürfen, ignorieren Sie einfach leere Zeilen.

Beachten Sie auch, dass readZeilen nicht einfach gelesen werden. Es ignoriert führende und nachfolgende Leerzeichen, was für eine URL wahrscheinlich wünschenswert ist. Backslash am Ende einer Zeile wird als Escape-Zeichen behandelt, wodurch die nächste Zeile mit der ersten minus der Backslash-Newline-Sequenz verbunden wird, was definitiv nicht wünschenswert ist. Sie sollten also die -rOption an übergeben read. Es ist sehr, sehr selten, dass readman eher das Richtige ist als read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Gilles 'SO - hör auf böse zu sein'
quelle
3

Nun, readgibt einen falsy Wert , wenn es am Ende der Datei , bevor eine neue Zeile trifft, aber selbst wenn es der Fall ist, weist es immer noch den Wert , den es zu lesen. Wir können also überprüfen, ob der letzte Aufruf von readetwas anderes als eine leere Zeile zurückgibt, und es wie gewohnt verarbeiten. Verlassen Sie die Schleife also erst, nachdem readfalse zurückgegeben wurde und die Zeile leer ist:

#!/bin/sh
while IFS= read -r line || [ "$line" ]; do 
    echo "line: $line"
done

$ printf 'foo\nbar' | sh ./read.sh 
line: foo
line: bar
$ printf 'foo\nbar\n' | sh ./read.sh 
line: foo
line: bar
ilkkachu
quelle
1

Ein anderer Weg wäre wie folgt:

Wenn der Lesevorgang das Dateiende anstelle des Zeilenende erreicht, werden die Daten eingelesen und den Variablen zugewiesen, sie werden jedoch mit einem Status ungleich Null beendet. Wenn Ihre Schleife "während des Lesens" aufgebaut ist, erledigen Sie Dinge

Anstatt den Status des Leseexits direkt zu testen, testen Sie ein Flag und lassen Sie den Lesebefehl dieses Flag aus dem Schleifenkörper heraus setzen. Auf diese Weise wird unabhängig vom Exit-Status der Lesevorgänge der gesamte Schleifenkörper ausgeführt, da das Lesen wie jeder andere nur einer der Befehle in der Schleife war und kein entscheidender Faktor dafür, ob die Schleife überhaupt ausgeführt wird.

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY 
done < /tmp/urlFile

Von hier verwiesen .

Hunter.S.Thompson
quelle
1
cat "/ tmp / urlFile" | während URL lesen
tun
    echo $ url
erledigt

Dies ist eine nutzlose Verwendung voncat .

Ironischerweise können Sie den catProzess hier durch etwas wirklich Nützliches ersetzen : ein Tool, über das POSIX-Systeme verfügen, um die fehlende neue Zeile hinzuzufügen und die Datei in eine richtige POSIX-Textdatei zu verwandeln.

sed -e '$ a \' "/ tmp / urlFile" | während -r url lesen
tun
    printf "% s \ n" "$ {url}"
erledigt

Weiterführende Literatur

JdeBP
quelle
1
Das Verhalten von sed wird von POSIX nicht angegeben, wenn die Eingabe jedoch nicht mit einem Zeilenumbruch endet. auch wenn Zeilen größer als LINE_MAX sind, während readin diesen Fällen das Verhalten von angegeben wird.
Stéphane Chazelas