Jak znaleźć pliki, które nie mają pustej linii na końcu?

9

Mam pliki w podkatalogach bieżącego katalogu, które mogą, ale nie muszą mieć nowych linii na końcu; jak mogę znaleźć pliki, które nie mają nowej linii na końcu?

Próbowałem tego:

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

ale to nie działa. awk 'END{print}' $filewypisuje linię przed pustą nową linią, taką samą jak tail -n 1 $file.

jcubic
źródło
@don_crissti Potrzebuję plików, które nie mają końcowej pustej linii.
jcubic
2
Czy mogę zapytać, dlaczego musisz znaleźć te pliki? Wydaje mi się, że ma to związek z faktem, że pliki tekstowe w unixie powinny być zakończone znakiem nowej linii (na przykład vi „prawie po cichu” doda jedną przy zapisywaniu), a kilka poleceń (tekstowych) zignoruje ostatnia linia, jeśli nie jest zakończona nową linią (wc, iirc .... ale są inne). I to może pomóc
Olivier Dulac
awk 'END{print}' $file : ignoruje to całkowicie zawartość pliku $, a po zakończeniu analizy wszystkich plików zawartych w pliku „$ plik” dodaje nowy wiersz. Ponieważ jest to jedyna rzecz, którą wypisuje polecenie awk, można go zastąpić: printf '\n'(bez żadnego mentino pliku $) i zrobić to samo. Myślę, że to nie było to, do czego dążyłeś (tj. Wydrukować ostatni wiersz pliku?)
Olivier Dulac,
@don_crissti: jeśli ostatni znak pliku nie jest znakiem nowej linii, to plik ten nie jest dokładnie posixowym plikiem TEXT. patrz: unix.stackexchange.com/a/263919/27616 . Zauważ, że wiele poleceń tekstowych (wc, na przykład) po prostu zignorować ten ostatni „linia”, jeśli nie jest zakończony znakiem nowej linii
Olivier Dulac
1
@OlivierDulac: gawk drukuje, cpodobnie jak FreeBSD, ale nie zauważyłem, że jest to udokumentowane jako zależne od implementacji: gnu.org/software/gawk/manual/… . Więc to nie zdarzy, ale nie zawsze.
dave_thompson_085 15.10.16

Odpowiedzi:

14

Aby wyjaśnić, \nznak LF (aka lub znak nowej linii) jest separatorem linii , a nie separatorem linii. Linia nie jest zakończona, chyba że zostanie zakończona znakiem nowej linii. Plik, który zawiera tylko a\nbnie jest poprawnym plikiem tekstowym, ponieważ zawiera znaki po ostatnim wierszu. To samo dotyczy pliku, który zawiera tylko a. Plik a\nzawierający jedną niepustą linię.

Tak więc plik, który kończy się co najmniej jedną pustą linią, kończy się dwoma znakami nowej linii lub zawiera pojedynczy znak nowej linii.

Gdyby:

 tail -c 2 file | od -An -vtc

Dane wyjściowe \nlub \n \nplik zawiera co najmniej jedną końcową pustą linię. Jeśli nic nie wypisuje, to jest to pusty plik, jeśli wypisuje <anything-but-\0> \n, to kończy się niepustym wierszem. Cokolwiek innego, to nie jest plik tekstowy.

Teraz, aby użyć tego do znalezienia plików, które kończą się pustą linią, OK, jest to wydajne (szczególnie w przypadku dużych plików), ponieważ odczytuje tylko dwa ostatnie bajty plików, ale najpierw dane wyjściowe nie są łatwe do analizy programowej, szczególnie biorąc pod uwagę, że są niespójne między odkolejnymi implementacjami i musielibyśmy uruchomić jeden taili jeden odna plik.

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

(aby znaleźć pliki kończące się pustą linią) uruchomiłoby jak najmniej poleceń, ale oznaczałoby odczytanie pełnej zawartości wszystkich plików.

Najlepiej byłoby, gdybyś potrzebował powłoki, która może sama odczytać koniec pliku.

Z zsh:

zmodload zsh/system
for f (**/*(D.L+0)) {
  {
    sysseek -w end -2
    sysread
    [[ $REPLY = $'\n' || $REPLY = $'\n\n' ]] && print -r -- $f
  } < $f
}
Stéphane Chazelas
źródło
sposób na wykorzystanie metody tej odpowiedzi, aby wiedzieć, czy jakiś plik (ów) plików tekstowych: 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 ; }. Użyj jako:if ( are_textfiles this that otherthing ) ; then echo all are text files ; else echo "are_textfiles returned : $?" ; fi
Olivier Dulac
6

Z gnu sedi powłoką jak zsh(lub bashz shopt -s globstar):

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

sprawdza to, czy ostatni wiersz każdego pliku nie jest pusty, jeśli tak, to drukuje nazwę pliku.
Jeśli chcesz coś przeciwnego (wydrukować nazwy plików, jeśli ostatnia linia jest pusta) po prostu zastąpić /./z/^$/

don_crissti
źródło
1
Nigdy wcześniej nie widziałem -sw akcji. Dziękuję GNU!
glenn jackman
Uwaga: opcja F istnieje od wersji sed 4.2.2 (22 grudnia 2012 r.)
Izaak
3

Prawidłowo zakończony plik tekstowy z pustym ostatnim wierszem kończy się na dwa \n.

Zatem oczekujemy, że to tail -c2musi być równe $'\n\n'.

Niestety rozszerzenia poleceń usuwają końcowe nowe wiersze. Będziemy potrzebować trochę ulepszeń.

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"

Możemy nawet trochę rozwinąć, aby sprawdzić, które pliki nie mają końca nowej linii:

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

Zauważ, że nowa linia może być zmieniona na coś podobnego w $'\r\nrazie potrzeby.
W takim przypadku zmień także tail -c2na tail -c4.

Izaak
źródło
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
Oskar Skog
źródło
1
to nie działa z pustymi plikami, ale mogę z tym żyć.
jcubic
Może być więcej błędów, ponieważ porównanie ciągów nie działa tak, jak się spodziewałem. Dodałem sprawdzenie pustych plików.
Oskar Skog,
Ach, ignoruje znaki nowej linii.
Oskar Skog,
Rozważmy bardziej czytelne cat $file 2>&1 /dev/null, lub jeśli jest to tylko Bash, cat $file &> /dev/null.
kot
1
Zastanów się też nad cytowaniem $filewszędzie tam, gdzie jest używany - i $(commands ...)zamiast tego używaj zamiast `backticks`...
kota