Bei der Verwendung von Versionskontrollsystemen ärgere ich mich über das Geräusch, wenn der Diff sagt No newline at end of file
.
Also habe ich mich gefragt: Wie füge ich eine neue Zeile am Ende einer Datei hinzu, um diese Nachrichten loszuwerden?
Bei der Verwendung von Versionskontrollsystemen ärgere ich mich über das Geräusch, wenn der Diff sagt No newline at end of file
.
Also habe ich mich gefragt: Wie füge ich eine neue Zeile am Ende einer Datei hinzu, um diese Nachrichten loszuwerden?
Antworten:
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ätzlichen Parameter wird ein optionales Muster verwendet, das in einigen Fällen nützlich sein kann, wenn Sie den Vorgang auf bestimmte Dateien / Verzeichnisse beschränken möchten. Alternativ können Sie find -print0 ...
betroffene Dateien mit ähnlichen Programmen NUL
auflisten. Vergewissern Sie sich nur, dass sie durch -begrenzte Einträge enthalten.
while IFS= read -rd '' f; do ... done
Durchläuft die Einträge und behandelt sicher Dateinamen, die Leerzeichen und / oder Zeilenumbrüche enthalten.
tail -c1 < "$f"
Liest das letzte Zeichen aus einer Datei.
read -r _
Beendet mit einem Exit-Status ungleich Null, wenn eine nachgestellte Zeile fehlt.
|| echo >> "$f"
Hängt eine neue Zeile an die Datei an, wenn der Beendigungsstatus des vorherigen Befehls ungleich Null war.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
das Sie vor dem Bearbeiten von Dateien bewahrt, die in der Versionskontrolle nicht erfasst werden.
IFS=
Trennzeichens zum Deaktivieren des Trennzeichens ist gut, um das umgebende Leerzeichen beizubehalten. Die nullterminierten Einträge sind nur relevant, wenn Sie Dateien oder Verzeichnisse mit einem Zeilenvorschub im Namen haben, was irgendwie weit hergeholt zu sein scheint, aber die korrektere Art ist, mit dem generischen Fall umzugehen, stimme ich zu. Nur eine kleine Einschränkung: Die -d
Option zu read
ist in POSIX sh nicht verfügbar.
tail -n1 < "$f"
, um Probleme mit Dateinamen zu vermeiden, die mit beginnen -
( tail -n1 -- "$f"
funktioniert nicht für die aufgerufene Datei -
). Möglicherweise möchten Sie klarstellen, dass die Antwort jetzt zsh / bash-spezifisch ist.
sed -i -e '$a\' file
Und alternativ für OS X sed
:
sed -i '' -e '$a\' file
Dies wird nur dann \n
am Ende der Datei hinzugefügt, wenn sie nicht bereits mit einem Zeilenumbruch endet. Wenn Sie es also zweimal ausführen, wird keine 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
man sed
: $ Match the last line.
Aber vielleicht funktioniert es nur durch Zufall. Ihre Lösung funktioniert auch.
$
mit der letzten Zeile übereinstimmt, warum fügt es einer Zeichenfolge, die bereits eine neue Zeile enthält, keine neue Zeile hinzu?
$
. In einem regulären Ausdruck, wie z. B. dem Formular /<regex>/
, hat es die übliche Bedeutung "Zeilenende abgleichen". Andernfalls gibt sed als Adresse die spezielle Bedeutung "Letzte Zeile in Datei" an. Der Code funktioniert, da sed standardmäßig eine neue Zeile an die Ausgabe anfügt, sofern diese noch nicht vorhanden ist. Der Code "$ a \" sagt nur "Entspricht der letzten Zeile der Datei und fügt nichts hinzu." Aber implizit fügt sed die neue Zeile zu jeder verarbeiteten Zeile (wie dieser $
Zeile) hinzu, wenn sie nicht bereits vorhanden ist.
/regex/
erhält es eine andere Bedeutung. Die FreeBSD-Hilfeseiten sind meiner Meinung nach ein wenig informativer: freebsd.org/cgi/man.cgi?query=sed
Guck mal:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
so echo "" >> noeol-file
sollte es tun. (Oder wollten Sie darum bitten, diese Dateien zu identifizieren und zu reparieren?)
edit entfernte den ""
von echo "" >> foo
(siehe @ yuyichaos Kommentar)
edit2 fügte den ""
erneut hinzu ( aber siehe @Keith Thompsons Kommentar)
""
ist nicht notwendig (zumindest für bash) und tail -1 | wc -l
kann verwendet werden, um die datei ohne neue
""
ist nicht notwendig für bash, aber ich habe echo
Implementierungen gesehen , die nichts ausgeben , wenn sie ohne Argumente aufgerufen werden (obwohl keine von denen, die ich jetzt finde, dies tun). echo "" >> noeol-file
ist wahrscheinlich etwas robuster. printf "\n" >> noeol-file
ist noch mehr so.
csh
‚s echo
ist derjenige Ausgang nichts bekannt , wenn kein Argument übergeben. Aber wenn wir nicht-Bourne-artige Shells unterstützen wollen, sollten wir es schaffen, echo ''
anstatt echo ""
wie echo ""
es zum Beispiel ""<newline>
mit rc
oder ausgegeben würde es
.
tcsh
im Gegensatz dazu csh
wird beim Aufrufen ohne Argumente eine neue Zeile gedruckt - unabhängig von der Einstellung von $echo_style
.
Eine andere Lösung mit ed
. Diese Lösung wirkt sich nur auf die letzte Zeile aus und nur, wenn \n
Folgendes fehlt:
ed -s file <<< w
Das Öffnen der Datei zum Bearbeiten funktioniert im Wesentlichen über ein Skript. Das Skript ist der einzige w
Befehl, mit dem die Datei auf die Festplatte zurückgeschrieben wird. Es basiert auf diesem Satz in der ed(1)
Manpage:
EINSCHRÄNKUNGEN (...) Wenn eine Textdatei (nicht binär) nicht durch ein Zeilenvorschubzeichen abgeschlossen wird, Dann fügt ed einen zum Lesen / Schreiben hinzu. Im Falle einer Binärdatei file, ed fügt beim Lesen / Schreiben keine neue Zeile hinzu.
Eine einfache, tragbare, POSIX-kompatible Möglichkeit, einer Textdatei einen fehlenden, endgültigen Zeilenumbruch hinzuzufügen, ist:
[ -n "$(tail -c1 file)" ] && echo >> file
Bei diesem Ansatz muss nicht die gesamte Datei gelesen werden. es kann einfach nach EOF suchen und von dort aus arbeiten.
Dieser Ansatz muss auch keine temporären Dateien hinter Ihrem Rücken erstellen (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 kein Zeilenumbruch ist.
Wenn das letzte Byte der Datei ein Zeilenumbruch ist, gibt tail ihn zurück, und die Befehlsersetzung entfernt ihn. 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 noch eine nicht leere Textdatei mit einer leeren Zeile ist.
yash
wenn das letzte Zeichen in der Datei ein Mehrbytezeichen ist (z. B. in UTF-8-Ländereinstellungen) oder wenn das Gebietsschema C ist und für das letzte Byte in der Datei das 8. Bit festgelegt ist. Bei anderen Shells (mit Ausnahme von zsh) wird keine neue Zeile hinzugefügt, wenn die Datei mit einem NUL-Byte endet (dies bedeutet jedoch, dass die Eingabe auch nach dem Hinzufügen einer neuen Zeile kein Text ist).
Neue Zeile hinzufügen, unabhängig davon:
echo >> filename
Hier ist eine Möglichkeit, mit Python zu überprüfen, ob am Ende eine neue Zeile vorhanden ist, bevor eine hinzugefügt wird:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
echo ""
scheint robuster als echo -n '\n'
. Oder Sie könntenprintf '\n'
Die schnellste Lösung ist:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
Ist echt schnell.
Bei einer Datei mittlerer Größe seq 99999999 >file
dauert dies Millisekunden.
Andere Lösungen brauchen viel Zeit:
[ -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
Funktioniert mit ash, bash, lksh, mksh, ksh93, attsh und zsh, aber nicht mit yash.
Wenn Sie eine Lösung benötigen, die portabel ist (und alle anderen oben aufgelisteten Shells), wird sie möglicherweise etwas komplexer:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
Der schnellste Weg, um zu testen, ob das letzte Byte einer Datei ein Zeilenumbruch ist, besteht darin, nur das letzte Byte zu lesen. Das könnte man mit machen tail -c1 file
. Die vereinfachte Methode zum Testen, ob es sich bei dem Bytewert um eine neue Zeile handelt, schlägt jedoch fehl, je nachdem, wie in der Shell eine nachfolgende neue Zeile in einer Befehlserweiterung normalerweise entfernt wird. 8 wert.
Die richtige, POSIX-konforme, 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 vergleichen 0A
, erhalten Sie einen robusten Test.
Es ist hilfreich, zu vermeiden, dass einer ansonsten leeren Datei eine neue Zeile hinzugefügt wird.
Datei, die 0A
natürlich kein letztes Zeichen liefert von:
f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"
Kurz und bündig. Dies nimmt sehr wenig Zeit in Anspruch, da nur das letzte Byte gelesen wird (Suche nach EOF). 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 keine neue Zeile hinzugefügt.
xxd
noch hexdump
solche gibt. Im POSIX-Werkzeugkasten od -An -tx1
muss der Hex-Wert eines Bytes ermittelt werden.
Am besten korrigieren Sie den Editor des Benutzers, der die Datei zuletzt bearbeitet hat. Wenn Sie der letzte sind, der die Datei bearbeitet hat - welchen Editor verwenden Sie, nehme ich an, Textmate ...?
emacs
füge keine neue Zeile am Ende der Datei hinzu.
(setq require-final-newline 'ask)
in meinem.emacs
Wenn Sie beim Verarbeiten einer Pipeline nur schnell eine neue Zeile hinzufügen möchten, gehen Sie wie folgt vor:
outputting_program | { cat ; echo ; }
Es ist auch POSIX-konform.
Dann können Sie es natürlich in eine Datei umleiten.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Vorausgesetzt, die Eingabe enthält keine Nullen:
paste - <>infile >&0
... würde ausreichen, eine neue Zeile immer nur an das Ende einer Infile anzuhängen, wenn noch keine vorhanden wäre. Und es muss nur die Eingabedatei einmal durchlesen, um es richtig zu machen.
paste infile 1<> infile
stattdessen brauchen .
Obwohl es die Frage nicht direkt beantwortet, ist hier ein verwandtes Skript, das ich geschrieben habe, um Dateien zu erkennen, die nicht in newline 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. Dies ist sehr schnell, da nicht der gesamte Inhalt jeder Datei gelesen werden muss. Es gibt für jede gelesene Datei eine Zeile mit dem Präfix "error:" aus, wenn ein Fehler auftritt, "empty:", wenn die Datei leer ist (endet nicht mit newline!), "EOL:" ("end of line ") wenn die Datei mit newline endet und" no EOL: "wenn die Datei nicht mit newline endet.
Hinweis: Das Skript verarbeitet keine Dateinamen, die Zeilenumbrüche enthalten. Wenn Sie auf einem GNU- oder BSD-System arbeiten, können Sie alle möglichen Dateinamen behandeln, indem Sie -print0 zu find, -z zu sortieren und -0 zu perl 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 sich noch eine Methode einfallen lassen, um die Dateinamen mit Zeilenumbrüchen in der Ausgabe zu codieren (als Übung für den Leser).
Falls gewünscht, kann die Ausgabe gefiltert werden, um eine neue Zeile an diejenigen Dateien anzuhängen, die keine haben, am einfachsten mit
echo >> "$filename"
Das Fehlen einer letzten Zeile kann zu Fehlern in Skripten führen, da einige Versionen von Shell und anderen Dienstprogrammen eine fehlende letzte Zeile beim Lesen einer solchen Datei nicht richtig behandeln.
Nach meiner Erfahrung wird das Fehlen einer letzten Zeile durch die Verwendung verschiedener Windows-Dienstprogramme zum Bearbeiten von Dateien verursacht. Ich habe noch nie gesehen, dass vim beim Bearbeiten einer Datei einen fehlenden abschließenden Zeilenumbruch verursacht, obwohl er über solche Dateien berichtet.
Schließlich gibt es viel kürzere (aber langsamere) Skripte, die ihre Dateinameneingaben durchlaufen können, um die Dateien zu drucken, die nicht mit Zeilenvorschub enden, wie zum Beispiel:
/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
Die vi
/ vim
/ ex
-Editoren fügen <EOL>
bei EOF automatisch etwas hinzu, es sei denn, die Datei enthält es bereits.
Also versuchen Sie 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 behebe ich "Keine Zeilenumbrüche am Ende der Datei" für viele Dateien? bei SO
Warum ist das so wichtig? Damit unsere Dateien POSIX-kompatibel bleiben .
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.
find .
alle Dateien aufgelistet werden, einschließlich der Dateien in .git
. find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Zumindest in den GNU - Versionen, einfach grep ''
oderawk 1
Kanonisiert seine Eingabe, das Hinzufügen eine abschließende Newline falls nicht bereits vorhanden. Sie kopieren die Datei während des Vorgangs, was einige Zeit in Anspruch nimmt, wenn sie groß ist (der Quellcode sollte jedoch nicht zu groß sein, um gelesen zu werden?) Und aktualisieren die Mod-Zeit, sofern Sie nicht etwas Ähnliches tun
mv file old; grep '' <old >file; touch -r old file
(Obwohl dies für eine Datei in Ordnung sein kann, die Sie einchecken, weil Sie sie geändert haben), und es verliert Hardlinks, nicht standardmäßige Berechtigungen und ACLs usw., es sei denn, Sie sind noch vorsichtiger.
grep '' file 1<> file
, obwohl das die Datei immer noch vollständig lesen und schreiben würde.
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 wc
Befehl einen Wert von zurück , wenn in der Datei die neue Zeile fehlt, 2
und wir schreiben eine neue Zeile.
Als Ergänzung zu Patrick Oscitys Antwort können Sie auch Folgendes verwenden , wenn Sie sie nur auf ein bestimmtes Verzeichnis anwenden möchten:
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 neue Zeilen hinzufügen möchten.
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 ein.
Wenn Ihre Datei mit Windows- Zeilenenden abgeschlossen ist \r\n
und Sie sich in Linux befinden, können Sie diesen sed
Befehl 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 ein enthält, \r\n
stimmt der reguläre Ausdruck der Suche nicht überein, daher geschieht nichts.
Sie könnten ein fix-non-delimited-line
Skript schreiben wie:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
if sysopen -rwu0 -- "$file"; then
if sysseek -w end -1; then
read -r x || print -u0
else
syserror -p "Can't seek in $file before the last byte: "
ret=1
fi
else
ret=1
fi
done
exit $ret
Im Gegensatz zu einigen der hier angegebenen Lösungen ist es
Sie können es zum Beispiel verwenden als:
that-script *.txt
oder:
git ls-files -z | xargs -0 that-script
POSIXly könnten Sie etwas tun, das mit funktionell gleichwertig ist
export LC_ALL=C
ret=0
for file do
[ -s "$file" ] || continue
{
c=$(tail -c 1 | od -An -vtc)
case $c in
(*'\n'*) ;;
(*[![:space:]]*) printf '\n' >&0 || ret=$?;;
(*) ret=1;; # tail likely failed
esac
} 0<> "$file" || ret=$? # record failure to open
done