Ich habe ein ... seltsames Problem mit einem Bash-Shell-Skript, auf das ich gehofft hatte, einen Einblick zu bekommen.
Mein Team arbeitet an einem Skript, das Zeilen in einer Datei durchläuft und in jeder Zeile nach Inhalten sucht. Wir hatten einen Fehler, bei dem beim Ausführen über den automatisierten Prozess, bei dem verschiedene Skripte zusammengeführt werden, die letzte Zeile nicht angezeigt wurde.
Der Code, der zum Durchlaufen der Zeilen in der Datei verwendet wird (Name gespeichert in DATAFILE
war
cat "$DATAFILE" | while read line
Wir könnten das Skript über die Befehlszeile ausführen und es würde jede Zeile in der Datei sehen, einschließlich der letzten, ganz gut. Bei Ausführung durch den automatisierten Prozess (der das Skript ausführt, das die DATENDATEI unmittelbar vor dem betreffenden Skript generiert) wird die letzte Zeile jedoch nie angezeigt.
Wir haben den Code aktualisiert, um Folgendes zu verwenden, um die Zeilen zu durchlaufen, und das Problem wurde behoben:
for line in `cat "$DATAFILE"`
Hinweis: In DATAFILE wurde am Ende der Datei noch nie eine neue Zeile geschrieben.
Meine Frage besteht aus zwei Teilen ... Warum wird die letzte Zeile vom Originalcode nicht gesehen und warum ändert sich dies?
Ich dachte nur, ich könnte mir überlegen, warum die letzte Zeile nicht zu sehen ist:
- Der vorherige Prozess, der die Datei schreibt, war darauf angewiesen, dass der Prozess beendet wird, um den Dateideskriptor zu schließen.
- Das Problemskript wurde gestartet und die Datei zuvor so schnell geöffnet, dass der vorherige Prozess zwar "beendet", aber nicht so heruntergefahren / bereinigt wurde, dass das System den Dateideskriptor automatisch schließen konnte.
Abgesehen davon scheint es so, als ob, wenn Sie zwei Befehle in einem Shell-Skript haben, der erste vollständig heruntergefahren werden sollte, wenn das Skript den zweiten ausführt.
Jeder Einblick in die Fragen, insbesondere die erste, wäre sehr dankbar.
cat somefile | while read
dass alle in derwhile
Schleife festgelegten Variablen beim Beenden der Schleife zerstört werden. Sie wollen wahrscheinlichwhile read ...; done <somefile
stattdessen; siehe BashFAQ # 24 .Antworten:
Der C-Standard besagt, dass Textdateien mit einem Zeilenumbruch enden müssen, da sonst die Daten nach dem letzten Zeilenumbruch möglicherweise nicht richtig gelesen werden.
Ich hätte nicht unerwartet einen fehlenden Zeilenumbruch am Ende der Datei, der Probleme in
bash
(oder einer Unix-Shell) verursachen könnte, aber das scheint das Problem reproduzierbar zu sein ($
ist die Eingabeaufforderung in dieser Ausgabe):$ echo xxx\\c xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y $ cat y abc def ghi xxx$ $ while read line; do echo $line; done < y abc def ghi $ bash -c 'while read line; do echo $line; done < y' abc def ghi $ ksh -c 'while read line; do echo $line; done < y' abc def ghi $ zsh -c 'while read line; do echo $line; done < y' abc def ghi $ for line in $(<y); do echo $line; done # Preferred notation in bash abc def ghi xxx $ for line in $(cat y); do echo $line; done # UUOC Award pending abc def ghi xxx $
Es ist auch nicht auf
bash
- Korn shell (ksh
) beschränkt undzsh
verhält sich auch so. Ich lebe, ich lerne; Vielen Dank, dass Sie das Problem angesprochen haben.Wie im obigen Code gezeigt,
cat
liest der Befehl die gesamte Datei. Diefor line in `cat $DATAFILE`
Technik sammelt alle Ausgaben und ersetzt beliebige Sequenzen von Leerzeichen durch ein einzelnes Leerzeichen (ich schließe daraus, dass jede Zeile in der Datei keine Leerzeichen enthält).Getestet unter Mac OS X 10.7.5.
Was sagt POSIX?
Die POSIX-
read
Befehlsspezifikation lautet:Beachten Sie, dass '(falls vorhanden)' (Hervorhebung im Zitat hinzugefügt)! Es scheint mir, dass wenn es keine neue Zeile gibt, es immer noch das Ergebnis lesen sollte. Auf der anderen Seite heißt es auch:
und dann kehren Sie zur Debatte zurück, ob eine Datei, die nicht mit einem Zeilenumbruch endet, eine Textdatei ist oder nicht.
Die Begründung auf derselben Seite dokumentiert jedoch:
Diese Begründung muss bedeuten, dass die Textdatei mit einem Zeilenumbruch enden soll.
Die POSIX-Definition einer Textdatei lautet:
Dies legt nicht fest, dass 'endet mit einer <newline>' direkt, sondern widerspricht dem C-Standard.
Eine Lösung für das Problem "No Terminal Newline"
Hinweis Gordon Davisson ‚s Antwort . Ein einfacher Test zeigt, dass seine Beobachtung korrekt ist:
$ while read line; do echo $line; done < y; echo $line abc def ghi xxx $
Daher ist seine Technik von:
while read line || [ -n "$line" ]; do echo $line; done < y
oder:
cat y | while read line || [ -n "$line" ]; do echo $line; done
funktioniert für Dateien ohne Zeilenumbruch am Ende (zumindest auf meinem Computer).
Ich bin immer noch überrascht, dass die Shells das letzte Segment (es kann nicht als Zeile bezeichnet werden, da es nicht mit einer neuen Zeile endet) der Eingabe löschen, aber in POSIX gibt es möglicherweise eine ausreichende Begründung dafür. Und natürlich ist es am besten sicherzustellen, dass Ihre Textdateien wirklich Textdateien sind, die mit einem Zeilenumbruch enden.
quelle
read
weil es vor 30 Jahren gut funktioniert hat und immer noch für mich funktioniert. Moderner Stil ist zu verwenden,read -r
weil erread
durch den POSIX-Prozess geschlachtet wurde. Ihr Anruf - Ich werde nicht beleidigt sein, wenn Sie ihn verwendenread -r
, solange Sie erklären können, wovor er Sie im Vergleich zur Verwendung schütztread
, und Sie können erklären, warum Ihnen dieser Schutz am Herzen liegt.printf '\n' | cat myfile.txt - | while IFS= read -r VAR; do echo "$VAR"; done
Gemäß der POSIX-Spezifikation für den Lesebefehl sollte ein Status ungleich Null zurückgegeben werden, wenn "Dateiende erkannt wurde oder ein Fehler aufgetreten ist". Da EOF beim Lesen der letzten "Zeile" erkannt wird, wird
$line
ein Fehlerstatus festgelegt und anschließend zurückgegeben. Der Fehlerstatus verhindert, dass die Schleife in dieser letzten "Zeile" ausgeführt wird. Die Lösung ist einfach: Lassen Sie die Schleife ausführen, wenn der Lesebefehl erfolgreich ist ODER wenn etwas eingelesen wurde$line
.while read line || [ -n "$line" ]; do
quelle
y
ich ausgeführt:while read line; do echo $line; done < y; echo $line
und tatsächlich vier verschiedene Werte wiedergegeben. Ich bin nicht sicher, ob es ein besonders hilfreiches oder intuitives Verhalten ist, aber ...Zusätzliche Informationen hinzufügen:
cat
while-Schleife zu verwenden.while ...;do something;done<file
reicht.for
.Bei Verwendung der while-Schleife zum Lesen von Zeilen:
IFS
richtig ein (andernfalls können Sie die Einrückung verlieren).Wenn die oben genannten Anforderungen erfüllt sind, sieht eine ordnungsgemäße while-Schleife folgendermaßen aus:
while IFS= read -r line; do ... done <file
Und damit es mit Dateien ohne Zeilenumbruch am Ende funktioniert (meine Lösung von hier aus neu veröffentlichen ):
while IFS= read -r line || [ -n "$line" ]; do echo "$line" done <file
Oder
grep
mit while-Schleife verwenden:while IFS= read -r line; do echo "$line" done < <(grep "" file)
quelle
Verwenden Sie sed, um die letzte Zeile einer Datei abzugleichen. Anschließend wird eine neue Zeile angehängt, falls keine vorhanden ist, und die Datei wird inline ersetzt:
sed -i '' -e '$a\' file
Der Code stammt von diesem Stackexchange- Link
Hinweis: Ich habe leer Apostrophe hinzugefügt ,
-i ''
weil zumindest in OS X,-i
wurde mit-e
der Sicherungsdatei als Dateierweiterung. Ich hätte den ursprünglichen Beitrag gerne kommentiert, aber es fehlten 50 Punkte. Vielleicht bringt mir das ein paar in diesem Thread, danke.quelle
Ich habe dies in der Kommandozeile getestet
# create dummy file. last line doesn't end with newline printf "%i\n%i\nNo-newline-here" >testing
Testen Sie mit Ihrem ersten Formular (Rohrleitung zur while-Schleife)
cat testing | while read line; do echo $line; done
Dies übersieht die letzte Zeile, was sinnvoll ist, da
read
nur Eingaben erhalten werden, die mit einer neuen Zeile enden.Testen Sie mit Ihrem zweiten Formular (Befehlsersetzung)
for line in `cat testbed1` ; do echo $line; done
Dies erhält auch die letzte Zeile
read
Wird nur eingegeben, wenn es durch eine neue Zeile beendet wird. Deshalb verpassen Sie die letzte Zeile.Auf der anderen Seite in der zweiten Form
erweitert sich auf die Form von
Das wird durch die Shell in mehrere Felder mit IFS getrennt, so dass Sie erhalten
Deshalb bekommen Sie immer noch die letzte Zeile.
p / s: Was ich nicht verstehe ist, wie Sie das erste Formular zum Laufen bringen ...
quelle
Um dieses Problem zu umgehen, kann vor dem Lesen aus der Textdatei eine neue Zeile an die Datei angehängt werden.
echo "\n" >> $file_path
Dadurch wird sichergestellt, dass alle Zeilen, die zuvor in der Datei enthalten waren, gelesen werden.
quelle
Ich hatte ein ähnliches Problem. Ich habe eine Katze einer Datei erstellt, sie an eine Sortierung weitergeleitet und dann das Ergebnis an ein 'beim Lesen von var1 var2 var3' weitergeleitet. dh: cat $ FILE | sort -k3 | beim Lesen Count IP Name do Die Arbeit unter "do" war eine if-Anweisung, die sich ändernde Daten im Feld $ Name identifizierte und basierend auf Änderungen oder keiner Änderung Summen von $ Count ergab oder gedruckt wurde die summierte Zeile zum Bericht. Ich bin auch auf das Problem gestoßen, bei dem ich nicht die letzte Zeile zum Drucken in den Bericht bekommen konnte. Ich ging mit dem einfachen Mittel vor, die Katze / Sortierung in eine neue Datei umzuleiten, eine neue Zeile in diese neue Datei zu wiederholen und dann mein "beim Lesen des IP-Namens zählen" für die neue Datei mit erfolgreichen Ergebnissen auszuführen. dh: cat $ FILE | sort -k3> NEWFILE echo "\ n" >> NEWFILE cat NEWFILE | beim Lesen Count IP Name do Manchmal ist der einfache, unelegante Weg der beste.
quelle