it-swarm.com.de

Wie füge ich am Ende einer Datei eine neue Zeile hinzu?

Bei Verwendung von Versionskontrollsystemen ärgere ich mich über das Rauschen, wenn der Diff No newline at end of file Sagt.

Also habe ich mich gefragt: Wie füge ich am Ende einer Datei eine neue Zeile hinzu, um diese Nachrichten zu entfernen?

208
k0pernikus

Um ein Projekt rekursiv zu bereinigen, verwende ich diesen Oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

Erläuterung:

  • git ls-files -z listet Dateien im Repository auf. Als zusätzlicher Parameter wird ein optionales Muster verwendet, das in einigen Fällen hilfreich sein kann, wenn Sie den Vorgang auf bestimmte Dateien/Verzeichnisse beschränken möchten. Alternativ können Sie find -print0 ... oder ähnliche Programme zum Auflisten betroffener Dateien - stellen Sie nur sicher, dass NUL - begrenzte Einträge ausgegeben werden.

  • while IFS= read -rd '' f; do ... done durchläuft die Einträge und behandelt Dateinamen, die Leerzeichen und/oder Zeilenumbrüche enthalten, sicher.

  • tail -c1 < "$f" liest das letzte Zeichen aus einer Datei.

  • read -r _ wird mit einem Exit-Status ungleich Null beendet, wenn eine nachfolgende neue Zeile fehlt.

  • || echo >> "$f" hängt eine neue Zeile an die Datei an, wenn der Beendigungsstatus des vorherigen Befehls ungleich Null war.

46
Patrick Oscity

Los geht's :

sed -i -e '$a\' file

Und alternativ für OS X sed:

sed -i '' -e '$a\' file

Dies fügt \n am Ende der Datei nur , wenn sie nicht bereits mit einem Zeilenumbruch endet. Wenn Sie es also zweimal ausführen, wird keine weitere neue Zeile hinzugefügt:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
215
l0b0

Guck mal:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

also sollte echo "" >> noeol-file den Trick machen. (Oder wollten Sie nach der Identifizierung dieser Dateien fragen und sie reparieren?)

edit entfernte den "" aus echo "" >> foo (siehe @ yuyichaos Kommentar) edit2 fügte den "" erneut hinzu (aber siehe @Keith Thompsons Kommentar)

41
sr_

Eine andere Lösung mit ed. Diese Lösung wirkt sich nur auf die letzte Zeile aus und nur, wenn \n Fehlt:

ed -s file <<< w

Es funktioniert im Wesentlichen beim Öffnen der Datei zum Bearbeiten über ein Skript. Das Skript ist der einzelne Befehl w, mit dem die Datei auf die Festplatte zurückgeschrieben wird. Es basiert auf diesem Satz in ed(1) man page:

 EINSCHRÄNKUNGEN 
 (...) 
 
 Wenn eine Textdatei (nicht binär) nicht durch ein Zeilenumbruchzeichen beendet wird, 
 Dann ed fügt einen zum Lesen/Schreiben hinzu. Im Fall einer binären 
 Datei fügt ed beim Lesen/Schreiben keinen Zeilenumbruch hinzu. 
16
enzotib

Fügen Sie eine neue Zeile hinzu, unabhängig davon:

echo >> filename

Mit Python können Sie überprüfen, ob am Ende eine neue Zeile vorhanden ist, bevor Sie eine hinzufügen:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
15
Alexander

Eine einfache, tragbare und POSIX-kompatible Methode zum Hinzufügen einer fehlenden, endgültigen Zeilenumbruch zu einer Textdatei wäre:

[ -n "$(tail -c1 file)" ] && echo >> file

Dieser Ansatz muss nicht die gesamte Datei lesen. es kann einfach versuchen, EOF] und von dort aus zu arbeiten.

Bei diesem Ansatz müssen auch keine temporären Dateien hinter Ihrem Rücken erstellt werden (z. B. sed -i), sodass Hardlinks nicht betroffen sind.

echo fügt der Datei nur dann eine neue Zeile hinzu, wenn das Ergebnis der Befehlsersetzung eine nicht leere Zeichenfolge ist. Beachten Sie, dass dies nur passieren kann, wenn die Datei nicht leer ist und das letzte Byte keine neue Zeile ist.

Wenn das letzte Byte der Datei eine neue Zeile ist, gibt tail sie zurück, und die Befehlsersetzung entfernt sie. Das Ergebnis ist eine leere Zeichenfolge. Der -n-Test schlägt fehl und das Echo wird nicht ausgeführt.

Wenn die Datei leer ist, ist das Ergebnis der Befehlsersetzung ebenfalls eine leere Zeichenfolge, und das Echo wird erneut nicht ausgeführt. Dies ist wünschenswert, da eine leere Datei weder eine ungültige Textdatei ist noch einer nicht leeren Textdatei mit einer leeren Zeile entspricht.

12
Barefoot IO

Die schnellste Lösung ist:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. Ist wirklich schnell.
    Auf einer mittelgroßen Datei seq 99999999 >file das dauert Millisekunden.
    Andere Lösungen dauern lange:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
  2. Funktioniert in Asche, Bash, Lksh, Mksh, Ksh93, Attsh und Zsh, aber nicht in Yash.

  3. Ändert den Dateizeitstempel nicht, wenn keine neue Zeile hinzugefügt werden muss.
    Alle anderen hier vorgestellten Lösungen ändern den Zeitstempel der Datei.
  4. Alle oben genannten Lösungen sind gültige POSIX.

Wenn Sie eine tragbare Lösung zum Yash benötigen (und alle anderen oben aufgeführten Shells), kann dies etwas komplexer werden:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
8
Isaac

Der schnellste Weg, um zu testen, ob das letzte Byte einer Datei eine neue Zeile ist, besteht darin, nur das letzte Byte zu lesen. Das könnte mit tail -c1 file Geschehen werden. Die vereinfachte Methode zum Testen, ob der Bytewert eine neue Zeile ist, schlägt jedoch in yash fehl, wenn das letzte Zeichen in der Datei ein UTF- ist. 8 Wert.

Die richtige, POSIX-kompatible, alle (vernünftigen) Shells-Methode, um festzustellen, ob das letzte Byte einer Datei eine neue Zeile ist, besteht darin, entweder xxd oder hexdump zu verwenden:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Wenn Sie dann die Ausgabe von oben mit 0A Vergleichen, erhalten Sie einen robusten Test.
Es ist nützlich zu vermeiden, einer ansonsten leeren Datei eine neue Zeile hinzuzufügen.
Datei, die kein letztes Zeichen von 0A Liefert, natürlich:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Kurz und bündig. Dies dauert sehr wenig Zeit, da nur das letzte Byte gelesen wird (EOF suchen). Es spielt keine Rolle, ob die Datei groß ist. Fügen Sie dann bei Bedarf nur ein Byte hinzu.

Keine temporären Dateien benötigt oder verwendet. Es sind keine Hardlinks betroffen.

Wenn dieser Test zweimal ausgeführt wird, wird nicht eine weitere neue Zeile hinzugefügt.

7
sorontar

Sie sollten den Editor des Benutzers korrigieren, der die Datei zuletzt bearbeitet hat. Wenn Sie die letzte Person sind, die die Datei bearbeitet hat - welchen Editor verwenden Sie, vermute ich textmate ..?

4
AD7six

Vorausgesetzt, die Eingabe enthält keine Nullen:

paste - <>infile >&0

... würde ausreichen, um immer nur eine neue Zeile an das hintere Ende einer Datei anzuhängen, wenn es noch keine gab. Und es muss nur die Eingabedatei einmal durchgelesen werden, um es richtig zu machen.

3
user157018

Wenn Sie bei der Verarbeitung einer Pipeline nur schnell eine neue Zeile hinzufügen möchten, verwenden Sie Folgendes:

outputting_program | { cat ; echo ; }

es ist auch POSIX-konform.

Dann können Sie es natürlich in eine Datei umleiten.

3
MichalH

Obwohl es die Frage nicht direkt beantwortet, ist hier ein verwandtes Skript, das ich geschrieben habe, um Dateien zu erkennen, die nicht in Zeilenumbruch enden. Es ist sehr schnell.

find . -type f | # sort |        # sort file names if you like
/usr/bin/Perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Das Perl-Skript liest eine Liste von (optional sortierten) Dateinamen aus stdin und liest für jede Datei das letzte Byte, um festzustellen, ob die Datei in einer neuen Zeile endet oder nicht. Es ist sehr schnell, da nicht der gesamte Inhalt jeder Datei gelesen werden muss. Für jede gelesene Datei wird eine Zeile mit dem Präfix "error:" ausgegeben, wenn ein Fehler auftritt, "empty:", wenn die Datei leer ist (endet nicht mit newline!), "EOL:" ("end of" Zeile "), wenn die Datei mit Zeilenumbruch endet, und" no EOL: ", wenn die Datei nicht mit Zeilenumbruch endet.

Hinweis: Das Skript verarbeitet keine Dateinamen, die Zeilenumbrüche enthalten. Wenn Sie sich auf einem GNU oder BSD-System) befinden, können Sie alle möglichen Dateinamen verarbeiten, indem Sie -print0 zum Suchen, -z zum Sortieren und -0 zum Perl wie folgt hinzufügen:

find . -type f -print0 | sort -z |
/usr/bin/Perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Natürlich müssten Sie noch eine Möglichkeit finden, die Dateinamen mit Zeilenumbrüchen in der Ausgabe zu codieren (als Übung für den Leser übrig).

Falls gewünscht, kann die Ausgabe gefiltert werden, um eine neue Zeile an die Dateien anzuhängen, die keine haben, am einfachsten mit

 echo >> "$filename"

Das Fehlen einer endgültigen neuen Zeile kann zu Fehlern in Skripten führen, da einige Versionen von Shell und anderen Dienstprogrammen eine fehlende endgültige neue Zeile beim Lesen einer solchen Datei nicht richtig behandeln.

Nach meiner Erfahrung wird das Fehlen eines endgültigen Zeilenumbruchs durch die Verwendung verschiedener Windows-Dienstprogramme zum Bearbeiten von Dateien verursacht. Ich habe noch nie gesehen, dass vim beim Bearbeiten einer Datei einen fehlenden letzten Zeilenumbruch verursacht, obwohl über solche Dateien berichtet wird.

Schließlich gibt es viel kürzere (aber langsamere) Skripte, die ihre Dateinameneingaben durchlaufen können, um die Dateien zu drucken, die nicht in Zeilenumbruch enden, wie z.

/usr/bin/Perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...

Die Editoren vi/vim/ex fügen automatisch <EOL> at EOF es sei denn, die Datei hat es bereits.

Versuchen Sie es also entweder:

vi -ecwq foo.txt

was äquivalent ist zu:

ex -cwq foo.txt

Testen:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

Um mehrere Dateien zu korrigieren, überprüfen Sie: Wie wird 'Kein Zeilenumbruch am Dateiende' für viele Dateien behoben? bei SO

Warum ist das so wichtig? Damit unsere Dateien erhalten bleiben POSIX-kompatibel .

1
kenorb

Wenn Ihre Datei mit Windows Zeilenenden \r\n Beendet wird und Sie sich unter Linux befinden, können Sie diesen Befehl sed verwenden. Es wird nur \r\n Zur letzten Zeile hinzugefügt, wenn es nicht bereits vorhanden ist:

sed -i -e '$s/\([^\r]\)$/\1\r\n/'

Erläuterung:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\1\r\n    replace it with itself and add \r\n

Wenn die letzte Zeile bereits einen \r\n Enthält, stimmt der reguläre Ausdruck der Suche nicht überein, daher geschieht nichts.

0
masgo

So wenden Sie die akzeptierte Antwort auf alle Dateien im aktuellen Verzeichnis (plus Unterverzeichnisse) an:

$ find . -type f -exec sed -i -e '$a\' {} \;

Dies funktioniert unter Linux (Ubuntu). Unter OS X müssen Sie wahrscheinlich -i '' (Ungetestet) verwenden.

0
friederbluemle

Sie könnten ein fix-non-delimited-line - Skript wie folgt schreiben:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  (){
    sysseek -w end -1 || {
      syserror -p "Can't seek before the last byte: "
      return 1
    }
    read -r x || print -u0
  } <> $file || ret=$?
done
exit $ret

Im Gegensatz zu einigen der hier angegebenen Lösungen ist es

  • sollte insofern effizient sein, als es keinen Prozess verzweigt, nur ein Byte für jede Datei liest und die Datei nicht neu schreibt (nur eine neue Zeile anfügt)
  • wird keine Symlinks/Hardlinks unterbrechen oder Metadaten beeinflussen (außerdem werden ctime/mtime nur aktualisiert, wenn eine neue Zeile hinzugefügt wird)
  • sollte auch dann einwandfrei funktionieren, wenn das letzte Byte ein NUL ist oder Teil eines Mehrbyte-Zeichens ist.
  • sollte OK funktionieren, unabhängig davon, welche Zeichen oder Nichtzeichen die Dateinamen enthalten können
  • Sollte mit unlesbaren oder nicht beschreibbaren oder nicht suchbaren Dateien richtig umgehen (und Fehler entsprechend melden)
  • Sollte keine neue Zeile zu leeren Dateien hinzufügen (meldet aber einen Fehler über eine ungültige Suche in diesem Fall)

Sie können es zum Beispiel verwenden als:

that-script *.txt

oder:

git ls-files -z | xargs -0 that-script
0

Hinzufügen zu Antwort von Patrick Oscity Wenn Sie es nur auf ein bestimmtes Verzeichnis anwenden möchten, können Sie auch Folgendes verwenden:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Führen Sie dies in dem Verzeichnis aus, zu dem Sie Zeilenumbrüche hinzufügen möchten.

0
cipher

echo $'' >> <FILE_NAME> fügt am Ende der Datei eine leere Zeile ein.

echo $'\n\n' >> <FILE_NAME> fügt am Ende der Datei 3 Leerzeilen hinzu.

0
user247137

Zumindest in den Versionen GNU, kanonisiert einfach grep '' Oder awk 1 seine Eingabe, Hinzufügen einer letzten neuen Zeile, falls nicht bereits vorhanden. Sie kopieren dabei die Datei, was einige Zeit in Anspruch nimmt, wenn sie groß ist (aber die Quelle sollte trotzdem nicht zu groß zum Lesen sein?), und aktualisiert die Modtime, es sei denn, Sie tun etwas Ähnliches

 mv file old; grep '' <old >file; touch -r old file

(obwohl dies für eine Datei, die Sie einchecken, in Ordnung sein kann, weil Sie sie geändert haben) und Hardlinks, nicht standardmäßige Berechtigungen und ACLs usw. verloren gehen, es sei denn, Sie sind noch vorsichtiger.

0

Viele großartige Vorschläge, aber eine Idee wäre, eine neue Zeile zu entfernen und eine hinzuzufügen, damit Sie wissen, dass Sie sie nicht ständig hinzufügen:

Nehmen Sie die Datei "foo":

Entfernen Sie eine neue Zeile, falls vorhanden:

  truncate -s $(($(stat -c '%s' foo)-1)) foo

Dann fügen Sie eine hinzu:

  sed -i -e '$a\' foo

Daher enthält foo immer mindestens eine neue Zeile.

oder beenden Sie die Datei und suchen Sie nach einer neuen Zeile, falls diese keine enthält.

  grep -q "[^0-9a-z-]" <<< $(tail -1 ./foo) && echo " " >> ./foo
0
Mike Q

Dies funktioniert in AIX ksh:

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi

In meinem Fall gibt der Befehl wc den Wert 2 Zurück, wenn in der Datei die neue Zeile fehlt, und wir schreiben eine neue Zeile.

0
Daemoncan