Jak znaleźć pliki zawierające 100% NUL znaków w swojej zawartości?

16

Jakie polecenie wiersza polecenia systemu Linux może identyfikować takie pliki?

AFAIK findpolecenie (lub grep) może dopasować tylko określony ciąg znaków w pliku tekstowym. Ale chcę dopasować całą zawartość, tzn. Chcę zobaczyć, które pliki pasują do wyrażenia regularnego \0+, ignorując znaki końca linii . Może find . cat | grepidiom może działać, ale nie wiem, jak zrobić grep ignorując linie (i traktować plik jako binarny).

Tło: Co kilka dni, kiedy mój laptop zawiesza się, moja partycja btrfs traci informacje: pliki otwarte do zapisu zostają zastąpione zerami (rozmiar pliku pozostaje mniej więcej nienaruszony). Korzystam z synchronizacji i nie chcę, aby te fałszywe pliki się rozprzestrzeniały: potrzebuję sposobu ich zidentyfikowania, aby móc pobrać je z kopii zapasowej.

Adam Ryczkowski
źródło
masz na myśli pliki z zerami numerycznymi?
Rahul Patil,
2
Myślę, że chodzi raczej o znaki NULL niż zera numeryczne.
gertvdijk
10
Cofnijmy się tutaj. Co kilka dni, kiedy Twój laptop zawiesza się? Dlaczego nie staramy się ustalić , że prawdziwy problem tutaj?
D_Bye
2
@D_Bye to dobry pomysł, ale jak dotąd nie zaszedł za daleko: [ unix.stackexchange.com/questions/57894/...
Adam Ryczkowski,
1
czy rozważałeś -vopcję grep: odfiltruj wszystkie pliki, które mają dowolny bajt od 1 do 255.
ctrl-alt-delor

Odpowiedzi:

10

Możesz grepdla ␀ znaków w trybie wyrażenia regularnego Perl:

$ echo -ne "\0\0" > nul.bin
$ echo -ne "\0x\0" > non-nul.bin
$ grep -P "[^\0]" *.bin
Binary file non-nul.bin matches

Możesz więc użyć tego:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done
l0b0
źródło
Korzystam z nieoczekiwanych wyników GNU grep 2.5.4. Niezależnie od tego, czy mogę użyć --binary-files=textlub --binary-files=binary, daje truewynik dla wszystkich niepustych wartości danych, np. "\0\0", "\0x\0", "abcd"... Dokładny kod użyłem to: for typ in binary text ;do for dat in '\0\0' '\0x\0' 'abcd' '' ;do printf "$dat" >f; grep --binary-files=$typ -P '[^\0]' f >/dev/null && echo true || echo false; done; done
Peter.O
1
Teraz próbowałem dalej GNU grep) 2.10. Ta późniejsza wersja daje oczekiwane wyniki ... więc spóźniona +1
Peter.O
1
Nie powiedzie się w przypadku pliku utworzonego za pomocą printf '\0\n\0\0\n\n' > filelub printf '\n' > filew tym przypadku.
Stéphane Chazelas
2
@ StéphaneChazelas OP powiedział „ignorując znak (i) końca linii”. Tak więc każdy plik składający się tylko \0ze \nznaków i (nawet zero każdego z nich) byłby zgodny.
l0b0
6

Zgadzam się z tym, co mówi D_Bye o znalezieniu źródła problemu.

W każdym razie, aby sprawdzić, czy plik zawiera tylko \0i / lub \nmożna użyć tr:

<file tr -d '\0\n' | wc -c

Który zwraca 0 dla pustych / nowych linii i pustych plików.

Thor
źródło
2
tr -d '\0\n'rozwiązuje problem nowej linii, który wtedy pozostawia tylko problem (?) pustych plików wymienionych na wyjściu ... Przetwarza jednak każdy bajt każdego pliku (który może, ale nie musi być problemem) +1
Peter.O
@ Peter.O: Brakowało mi wymogu nowej linii, dziękuję. To rozwiązanie nie jest bardzo zoptymalizowane, a jeśli ma działać na dużej ilości danych, byłoby lepiej z rozwiązaniem, które porusza się po znalezieniu niepasujących bajtów.
Thor
Pracuje bardzo dobrze. W moim przypadku musiałem tylko wykluczyć pliki o zerowej długości. Dziękuję Ci.
Adam Ryczkowski,
1
Będzie to jednak również liczyć pliki z nowymi wierszami jako „puste”.
Chris Down,
1
@ChrisDown: Wyjaśniłem tekst odpowiedzi, co robi. Nie jest jasne, co OP chce zrobić z plikami tylko w nowej linii.
Thor
5

Podejrzewam, że te pliki są rzadkie, to znaczy, że nie mają przydzielonego miejsca na dysku, po prostu określają rozmiar pliku ( duzgłosiłby dla nich 0).

W takim przypadku z GNU find możesz to zrobić (zakładając, że żadna ścieżka pliku nie zawiera znaków nowej linii):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-
Stéphane Chazelas
źródło
Słuszna uwaga. Nigdy o tym nie myślałem. Spróbuję. Użycie duzapobiegnie zarysowaniu zawartości każdego pojedynczego pliku w systemie plików, więc cała procedura nie zajmie ponad 30 minut.
Adam Ryczkowski
(i printf %bpowyżej podaje, co duby się zgłosiło)
Stéphane Chazelas,
Zmieniłbym -size +0na -size +1pliki o zerowej długości, które są wykluczone z wyników. Również pliki zawierające \nw swojej ścieżce powodują problemy z tym poleceniem.
Tyson
@Tyson -size +0jest dla rozmiarów ściśle większych niż 0. -size +1byłoby dla rozmiarów ściśle większych niż 512. Ograniczenie nowej linii zostało już wspomniane.
Stéphane Chazelas
@ StéphaneChazelas Dzięki za oświecenie mnie -size +1, że naprawdę masz rację. Naprawiłem swoją odpowiedź. :-)
Tyson
4

Oto mały program w języku Python, który potrafi to zrobić:

import sys

def only_contains_nulls(fobj, chunk_size=1024):
    first = True
    while True:
        data = fobj.read(chunk_size)
        if not data:
            if first:
                return 1  # No data
            else:
                return 0
        if data.strip("\0"):
            return 1
        first = False

if __name__ == '__main__':
    with open(sys.argv[1]) as f:
        sys.exit(only_contains_nulls(f))

I w akcji:

$ printf '\0\0\0' > file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Only nulls
$ printf a >> file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Non-null characters

Można sprawdzić wiele plików za pomocą znalezisko'S -exec, xargsGNU paralleli podobnych programów. Alternatywnie spowoduje to wydrukowanie nazw plików, z którymi należy się uporać:

files=( file1 file2 )
for file in "${files[@]}"; do
    ./onlynulls "$file" || printf '%s\n' "$file"
done

Pamiętaj, że jeśli zamierzasz przekazać dane wyjściowe temu innemu programowi, nazwy plików mogą zawierać znaki nowej linii, więc powinieneś je rozgraniczać inaczej (odpowiednio, za pomocą \0).

Jeśli masz dużo plików, lepiej byłoby użyć opcji równoległego przetwarzania, ponieważ odczytuje to tylko jeden plik na raz.

Chris Down
źródło
2
Strzeż się, zero pliki długość (np: /etc/nologin, ~/.hushlogin, .nomedia, ...) są błędnie przez tę odpowiedź.
Tyson
@Tyson Dzięki za wskazanie tego! Właśnie to naprawiłem.
Chris Down
3

Znajdź pliki zawierające tylko znaki zerowe „0” i znaki nowego wiersza „n”.
Funkcja qin sed powoduje, że każde wyszukiwanie pliku kończy się natychmiast po znalezieniu w wierszu dowolnego znaku o wartości innej niż zero.

find -type f -name 'file-*' |
  while IFS= read -r file ;do 
      out=$(sed -n '1=; /^\x00\+$/d; i non-null
                      ; q' "$file")
      [[ $out == "1" ]] &&  echo "$file"
  done

Utwórz pliki testowe

> file-empty
printf '%s\n' 'line1' 'line2' 'line3'      > file-with-text           
printf '%4s\n' '' '' xx | sed 's/ /\x00/g' > file-with-text-and-nulls
printf '%4s\n' '' '' '' | sed 's/ /\x00/g' > file-with-nulls-and-newlines
printf '%4s'   '' '' '' | sed 's/ /\x00/g' > file-with-nulls-only

wynik

./file-with-nulls-and-newlines
./file-with-nulls-only
Peter.O
źródło
Albo -print0argument wydaje się być brakuje findlub IFS=część jest pomieszane. Jaki był zamierzony ogranicznik?
Tyson
3

To jedna wkładka jest najbardziej skutecznym sposobem na znalezienie 100% przy użyciu plików nul GNU find, xargsoraz grep(zakładając, że jest ona zbudowana z obsługą PCRE):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00]" --

Zalety tej metody w porównaniu z innymi podanymi odpowiedziami to:

  • pliki nieliczne są uwzględniane podczas wyszukiwania.
  • nieczytelne pliki nie są przekazywane do grep, dzięki czemu można uniknąć Permission deniedostrzeżeń.
  • grepprzestanie czytać dane z plików po znalezieniu dowolnego bajtu innego niż nul ( LC_ALL=Csłuży do upewnienia się, że każdy bajt jest interpretowany jako znak ).
  • puste pliki (zero bajtów) nie są uwzględniane w wynikach.
  • mniej grepprocesów skutecznie sprawdza wiele plików.
  • ścieżki zawierające znaki nowej linii lub rozpoczynające się od -są obsługiwane poprawnie.
  • działa na większości systemów wbudowanych, w których brakuje języka Python / Perl.

Przekazywanie -Zopcji grepi używanie xargs -r0 ...pozwala na wykonanie dalszych działań na plikach 100% NUL (np .: czyszczenie):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00]" -- |
  xargs -r0 rm --

Polecam również korzystanie z findopcji, -Paby uniknąć następujących dowiązań symbolicznych i -xdevaby uniknąć przechodzenia przez systemy plików (np. Zdalne montowanie, drzewa urządzeń, montowanie powiązań itp.).

Aby zignorować znaki końca linii , powinien działać następujący wariant (choć nie sądzę, że to dobry pomysł):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00\r\n]" --

Złożenie wszystkiego razem, w tym usunięcie niechcianych plików (100% znaków nul / nowej linii), aby zapobiec ich tworzeniu kopii zapasowej:

find -P . -xdev -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00\r\n]" -- |
  xargs -0 rm --

Nie polecam dołączania pustych plików (zero bajtów), często istnieją one w bardzo specyficznych celach .

Tyson
źródło
Bycie najszybszym z tylu alternatyw to śmiałe twierdzenie. Zaznaczę Twoją odpowiedź jako zaakceptowaną, jeśli dodasz punkt odniesienia :-)
Adam Ryczkowski
Taki test zależałby od wielu czynników, w tym od wydajności różnych podsystemów dyskowych.
Tyson
Oczywiście, ale wszystko jest lepsze niż nic. Różne podejścia optymalizują użycie procesora w różny sposób, dlatego warto go przetestować na dysku SSD, a nawet w buforowanych plikach. Weź maszynę, na której aktualnie pracujesz, napisz jedno zdanie, czym ona jest (typ procesora, liczba rdzeni, pamięć RAM, typ dysku twardego), opisz zestaw plików (np. Klon źródła jądra + plik 1 GB pełen \0z 900 MB dziury) i aktualny czas wyników. Jeśli zrobisz to w sposób, który będzie dla ciebie przekonujący, najprawdopodobniej będzie przekonujący dla nas wszystkich
Adam Ryczkowski
„większość systemów wbudowanych” nie ma narzędzi GNU. Bardziej prawdopodobne te zajęte.
Stéphane Chazelas
-Pjest domyślnie w find. Jeśli chcesz podążać za dowiązaniami symbolicznymi, to -L/ -follow. Przekonasz się, że POSIX nawet nie określa tej opcji find(chociaż POSIX jest tym, który wprowadził te -P / -H / -L dla kilku poleceń).
Stéphane Chazelas
0

Do korzystania z GNU sed możesz użyć -zopcji, która definiuje linię jako łańcuchy zakończone zerem i dopasowują i usuwają puste linie w następujący sposób:

if [ "$( sed -z '/^$/d' "$file" | head -c 1 | wc -c )" -eq 0 ]; then
    echo "$file contains only NULL!"
fi

Polecenie główne pomiędzy nimi jest tylko optymalizacją.

mxmlnkn
źródło
-1

Pyton

Pojedynczy plik

Zdefiniuj alias:

alias is_binary="python -c 'import sys; sys.exit(not b\"\x00\" in open(sys.argv[1], \"rb\").read())'"

Sprawdź to:

$ is_binary /etc/hosts; echo $?
1
$ is_binary `which which`; echo $?
0

Wiele plików

Znajdź wszystkie pliki binarne rekurencyjnie:

IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} && echo {}" \;

Aby znaleźć wszystkie pliki niebinarne, zmień za &&pomocą ||.

kenorb
źródło
1
Pytanie zadane w celu zidentyfikowania plików zawierających tylko znaki nul (ignorując znaki nowej linii), podany tutaj kod Python identyfikuje pliki zawierające dowolne znaki nul.
Tyson