Wie finde ich Dateien, die am Ende keine Leerzeile haben?


9

Ich habe Dateien in Unterverzeichnissen des aktuellen Verzeichnisses, die am Ende möglicherweise neue Zeilen enthalten oder nicht. Wie finde ich Dateien, die am Ende keinen Zeilenumbruch haben?

Ich habe das versucht:

find . -name '*.styl' | while read file; do
    awk 'END{print}' $file | grep -E '^$' > /dev/null || echo $file;
done

aber es funktioniert nicht. awk 'END{print}' $filedruckt die Zeile vor einer leeren neuen Zeile, genau wie tail -n 1 $file.


@don_crissti Ich benötige Dateien ohne nachgestellte Leerzeile.
Jcubic

2
Darf ich fragen, warum Sie diese Dateien finden müssen? Ich denke , es mit der Tatsache zu tun, dass Text - Dateien in Unix werden soll mit einem Newline beendet werden (vi wird ein „fast geräuschlos“ hinzufügen , wenn Sie beispielsweise speichern,) und mehrere (textorientierte) Befehle ignorieren die letzte Zeile, wenn sie nicht durch eine neue Zeile abgeschlossen wird (wc, iirc .... aber es gibt andere). Und das kann helfen
Olivier Dulac

awk 'END{print}' $file : Dies ignoriert den Inhalt von $ file vollständig und fügt nach Abschluss des Parsens aller in "$ file" enthaltenen Dateien eine neue Zeile hinzu. Da es das einzige ist, was der Befehl awk druckt, kann er durch printf '\n'Folgendes ersetzt werden (ohne Mentino von $ file überhaupt) und dasselbe tun. Ich denke, das ist NICHT das, was Sie angestrebt haben (dh: die letzte Zeile der Datei drucken?)
Olivier Dulac

@don_crissti: Wenn das letzte Zeichen einer Datei kein Zeilenumbruch ist, ist diese Datei nicht unbedingt eine Unix-TEXT-Datei. Siehe: unix.stackexchange.com/a/263919/27616 . Beachten Sie, dass viele Textbefehle (z. B. wc) diese letzte "Zeile" einfach ignorieren, wenn sie nicht durch eine neue Zeile beendet wird
Olivier Dulac

1
@OlivierDulac: gawk druckt cund FreeBSD auch, aber ich hatte nicht bemerkt, dass es als implementierungsabhängig dokumentiert ist: gnu.org/software/gawk/manual/… . So kommt es aber nicht immer vor.
dave_thompson_085

Antworten:


14

Um zu klären, ist die LF (aka \noder Neues - Zeile) Zeichen sind die Zeilentrennzeichen , es ist nicht das Linientrenner. Eine Zeile wird erst beendet, wenn sie durch ein Zeilenumbruchzeichen abgeschlossen ist. Eine Datei, die nur enthält, a\nbist keine gültige Textdatei, da sie Zeichen nach der letzten Zeile enthält. Gleiches gilt für eine Datei, die nur enthält a. Eine Datei, die enthält, a\nenthält eine nicht leere Zeile.

Eine Datei, die mit mindestens einer Leerzeile endet, endet mit zwei Zeilenumbrüchen oder enthält ein einzelnes Zeilenumbruchzeichen.

Wenn:

 tail -c 2 file | od -An -vtc

Gibt \noder aus \n \n, dann enthält die Datei mindestens eine nachgestellte Leerzeile. Wenn es nichts ausgibt, ist das eine leere Datei. Wenn es ausgegeben wird <anything-but-\0> \n, endet es in einer nicht leeren Zeile. Alles andere ist keine Textdatei.

Um dies zu verwenden, um Dateien zu finden, die in einer leeren Zeile enden, ist dies insofern effizient (insbesondere bei großen Dateien), als nur die letzten zwei Bytes der Dateien gelesen werden. Zunächst ist die Ausgabe jedoch nicht einfach programmgesteuert zu analysieren, insbesondere wenn man bedenkt, dass dies der Fall ist nicht konsistent von einer Implementierung odzur nächsten, und wir müssten eine tailund eine odpro Datei ausführen .

find . -type f -size +0 -exec gawk '
  ENDFILE{if ($0 == "") print FILENAME}' {} +

(um Dateien zu finden, die in einer leeren Zeile enden) würde so wenig Befehle wie möglich ausführen, aber das Lesen des gesamten Inhalts aller Dateien bedeuten.

Idealerweise benötigen Sie eine Shell, die das Ende einer Datei selbst lesen kann.

Mit zsh:

zmodload zsh/system
for f (**/*(D.L+0)) {
  {
    sysseek -w end -2
    sysread
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f
}

Eine Möglichkeit, die Methode dieser Antwort zu verwenden, um festzustellen, ob es sich bei einigen Dateien um Textdateien handelt : are_textfiles () { nontext=0; rem="return 0 if all args are files with terminating newline, or n [=number of non-textfiles]" ; for f in "$@" ; do [ -f "$f" ] && { tail -c 1 "$f" | od -An -vtc | grep "\\n" ;} >/dev/null 2>&1 || ((nontext++)) ; done ; return $nontext ; }. Verwendung als:if ( are_textfiles this that otherthing ) ; then echo all are text files ; else echo "are_textfiles returned : $?" ; fi
Olivier Dulac

6

Mit gnu sedund einer Shell wie zsh(oder bashmit shopt -s globstar):

sed -ns '${/./F}' ./**/*.styl

Dadurch wird überprüft, ob die letzte Zeile jeder Datei nicht leer ist. In diesem Fall wird der Dateiname gedruckt.
Wenn Sie das Gegenteil wollen (Dateinamen drucken , wenn die letzte Zeile leer ist) ersetzen Sie einfach /./mit/^$/


1
Noch nie -sin Aktion gesehen. Danke GNU!
Glenn Jackman

Hinweis: Die Option F existiert ab sed Version 4.2.2 (22. Dezember 2012)
Isaac

3

Eine korrekt abgeschlossene Textdatei mit einer leeren letzten Zeile endet in zwei \n.

Dann erwarten wir, dass tail -c2das gleich sein muss $'\n\n'.

Befehlserweiterungen entfernen leider nachfolgende neue Zeilen. Wir müssen ein bisschen optimieren.

f=filename
nl='
'
t=$(tail -c2 $f; printf x)  # capture the last two characters.
r="${nl}${nl}$"                 # regex for: "ends in two newlines".
[[ ${t%x} =~ $r ]] &&  echo "file $f ends in an empty line"

Wir könnten sogar ein wenig erweitern, um zu überprüfen, welche Dateien keine nachfolgende neue Zeile haben:

nl='
'
nl=$'\n'
find . -type f -name '*.styl' | while read f; do
    t=$(tail -c2 $f; printf x); r1="${nl}$"; r2="${nl}${r1}"
    [[ ${t%x} =~ $r1 ]] || echo "file $f is missing a trailing newline"
    [[ ${t%x} =~ $r2 ]] && echo "$f"
done

Beachten Sie, dass die neue Zeile bei Bedarf geändert werden kann $'\r\n.
Wechseln Sie tail -c2in diesem Fall auch zu tail -c4.


0
for file in *; do
    # Check if the file is readable to avoid clutter
    if cat "./$file" 2&>1 /dev/null; then
        # Compare the last character with a single newline character.
        if [ -n "$(tail -c 1 -- "./$file")" ]; then
            echo "$file"
        fi
        # Also report empty files.
        if [ $(wc -c  < "./$file") -eq 0 ]; then
            echo "$file"
        fi
    fi
done

1
Das funktioniert nicht mit leeren Dateien, aber damit kann ich leben.
Jcubic

Möglicherweise treten weitere Fehler auf, da der Zeichenfolgenvergleich nicht wie erwartet zu funktionieren scheint. Ich habe eine Überprüfung auf leere Dateien hinzugefügt.
Oskar Skog

Ah, es ignoriert die Zeilenumbrüche.
Oskar Skog

Betrachten Sie die besser lesbare cat $file 2>&1 /dev/nulloder wenn dies nur Bash ist , cat $file &> /dev/null.
Katze

1
$file$(commands ...)`backticks`
Katze
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.