Come trovare i file con il 100% di caratteri NUL nei loro contenuti?


16

Qual è il comando da riga di comando di Linux che può identificare tali file?

AFAIK il findcomando (o grep) può corrispondere solo a una stringa specifica all'interno del file di testo. Ma voglio abbinare l'intero contenuto, cioè voglio vedere quali file corrispondono all'espressione regolare \0+, ignorando i caratteri di fine riga . Forse il find . cat | greplinguaggio potrebbe funzionare, ma non so come fare in modo che grep ignori le righe (e tratti il ​​file come binario).

Background: ogni pochi giorni, quando il mio laptop si blocca, la mia partizione btrfs perde informazioni: i file aperti per la scrittura vengono sostituiti con zero (la dimensione del file rimane più o meno intatta). Uso la sincronizzazione e non desidero propagare questi file falsi: ho bisogno di un modo per identificarli in modo da poterli catturare dal backup.


intendi file che contengono zeri numerici?
Rahul Patil,

2
Penso che si tratti di caratteri NULL piuttosto che di zeri numerici.
gertvdijk,

10
Facciamo un passo indietro qui. Ogni pochi giorni, quando il tuo laptop si blocca? Perché non stiamo cercando di risolvere che , il vero problema qui?
D_Bye,

2
@D_Bye è una buona idea, ma finora non è andata troppo lontano: [ unix.stackexchange.com/questions/57894/…
Adam Ryczkowski il

1
hai considerato l' -vopzione grep: filtra tutti i file che hanno un byte
compreso tra

Risposte:


10

Puoi grepper ␀ personaggi usando la modalità regex Perl:

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

Quindi puoi usare questo:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done

Ottengo risultati inaspettati, usando GNU grep 2.5.4. Indipendentemente dal fatto che io usi --binary-files=texto --binary-files=binary, dà un truerisultato per tutti i valori di dati non vuoti, ad es. "\0\0", "\0x\0", "abcd"... il codice esatto che ho usato è: 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
Ora ho ulteriormente provato GNU grep) 2.10. Questa versione successiva fornisce i risultati previsti ... quindi, +1 tardivo
Peter.O

1
Non riesce su un file creato con printf '\0\n\0\0\n\n' > fileo printf '\n' > fileper quello che conta.
Stéphane Chazelas,

2
@ StéphaneChazelas OP ha detto "ignorando i caratteri di fine riga". Quindi qualsiasi file composto da solo \0e \ncaratteri (anche zero di uno dei due) sarebbe una corrispondenza.
l0b0

6

Sono d'accordo con ciò che dice D_Bye sulla ricerca della radice del problema.

In ogni caso per verificare se un file contiene solo \0e / o \nè possibile utilizzare tr:

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

Che restituisce 0 per null / newline e file vuoti.


2
tr -d '\0\n'risolve il problema della nuova riga, che quindi lascia solo il problema (?) dei file vuoti che sono elencati nell'output ... Tuttavia elabora ogni byte di ogni file (che può essere o meno un problema) +1
Peter.O

@ Peter.O: ho perso il requisito newline, grazie. Questa soluzione non è molto ottimizzata e se deve essere eseguita su molti dati, sarebbe meglio con una soluzione che passa alla ricerca di byte non corrispondenti.
Thor,

Funziona molto bene Nel mio caso, dovevo solo assicurarmi di escludere file di lunghezza zero. Grazie.
Adam Ryczkowski il

1
Tuttavia, questo conterà anche i file con le nuove righe come "vuoti".
Chris Down,

1
@ChrisDown: ho chiarito il testo della risposta su ciò che fa. Non è chiaro cosa l'OP voglia fare con i file solo newline.
Thor,

5

Ho il sospetto che quei file siano sparsi, cioè che non abbiano spazio su disco assegnato a loro, semplicemente specificano una dimensione del file ( dusegnalerebbe 0 per loro).

Nel qual caso, con GNU find, potresti farlo (supponendo che nessun percorso di file contenga caratteri di nuova riga):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-

Buon punto. Non ci avevo mai pensato. Ci proverò. L'uso duimpedirà di graffiare il contenuto di ogni singolo file nel file system, quindi l'intera procedura non richiederebbe più di 30 minuti per essere completata.
Adam Ryczkowski il

(e printf %bsopra riporta ciò che duavrebbe riferito)
Stéphane Chazelas il

Vorrei cambiare -size +0in -size +1modo che i file di lunghezza zero siano esclusi dai risultati. Anche i file che contengono \nnel loro percorso causeranno problemi per questo comando.
Tyson,

@Tyson -size +0è per dimensioni strettamente superiori a 0. -size +1sarebbe per dimensioni strettamente superiori a 512. La limitazione di nuova riga era già stata menzionata.
Stéphane Chazelas il

@ StéphaneChazelas Grazie per avermi chiarito riguardo -size +1, hai davvero ragione. Ho corretto la mia risposta. :-)
Tyson il

4

Ecco un piccolo programma Python che può farlo:

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))

E in azione:

$ 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

È possibile selezionare più file utilizzando find delle -exec, xargs, GNU parallel, e programmi simili. In alternativa, verranno stampati i nomi dei file che devono essere trattati:

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

Tieni presente che se hai intenzione di passare l'output di questo ad un altro programma, i nomi dei file possono contenere nuove righe, quindi dovresti delimitarli in modo diverso (opportunamente, con \0).

Se hai molti file, sarebbe meglio usare un'opzione per l'elaborazione parallela, poiché questa legge solo un file alla volta.


2
Attenzione, i file di lunghezza pari a zero (per esempio: /etc/nologin, ~/.hushlogin, .nomedia, ...) vengono erroneamente identificati da questa risposta.
Tyson,

@Tyson Grazie per averlo sottolineato! L'ho appena risolto.
Chris Down,

3

Trova i file che contengono solo caratteri null '\ 0' e caratteri newline '\ n'.
L' qin sed cause ogni file cerca di smettere immediatamente su di trovare qualsiasi carattere non nullo in una linea.

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

Crea file di test

> 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

produzione

./file-with-nulls-and-newlines
./file-with-nulls-only

O l' -print0argomento sembra mancare findo la IFS=parte è incasinata. Qual era il delimitatore previsto?
Tyson,

3

Questa one-liner è il modo più efficace per trovare i file nul 100% utilizzando GNU find, xargse grep(supponendo che quest'ultimo è costruito con il supporto PCRE):

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

I vantaggi di questo metodo rispetto ad altre risposte fornite sono:

  • i file non sparsi sono inclusi nella ricerca.
  • i file non leggibili non vengono passati a grep, evitando Permission deniedavvisi.
  • grepinterromperà la lettura dei dati dai file dopo aver trovato un byte diverso da zero ( LC_ALL=Cviene utilizzato per assicurarsi che ogni byte sia interpretato come un carattere ).
  • i file vuoti (zero byte) non sono inclusi nei risultati.
  • meno grepprocessi controllano in modo efficiente più file.
  • i percorsi che contengono newline o che iniziano con -sono gestiti correttamente.
  • funziona sulla maggior parte dei sistemi embedded privi di Python / Perl.

Passare l' -Zopzione ae greputilizzare xargs -r0 ...consente di eseguire ulteriori azioni sui file nul al 100% (ad esempio: cleanup):

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

Consiglio inoltre di utilizzare le findopzioni -Pper evitare i seguenti collegamenti simbolici ed -xdevevitare di attraversare i filesystem (ad es. Montaggi remoti, alberi dei dispositivi, montaggi di bind, ecc.).

Per ignorare i caratteri di fine riga , la seguente variante dovrebbe funzionare (anche se non penso che sia una buona idea):

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

Mettendo tutto insieme, inclusa la rimozione dei file indesiderati (100% caratteri nul / newline) per impedire il backup:

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

Non consiglio di includere file vuoti (zero byte), spesso esistono per scopi molto specifici .


Essere il più veloce tra tante alternative è un'affermazione audace. Contrassegnerò la tua risposta come accettata se aggiungi un benchmark :-)
Adam Ryczkowski il

Tale benchmark dipenderebbe da molti fattori, tra cui le prestazioni dei vari sottosistemi di dischi.
Tyson,

Certo, ma tutto è meglio di niente. Vari approcci ottimizzano l'utilizzo della CPU in modo diverso, quindi ha senso confrontarlo su SSD o anche su file memorizzati nella cache. Prendi la macchina su cui stai attualmente lavorando, scrivi una frase di cosa si tratta (tipo di CPU, no di core, RAM, tipo di disco rigido), descrivi il set di file (ad esempio clone del kernel kernel + file da 1 GB pieno \0con un buco da 900 MB) e tempistica attuale dei risultati. Se lo fai in un modo che il benchmark è convincente per te, molto probabilmente sarà convincente per tutti noi
Adam Ryczkowski il

"la maggior parte dei sistemi embedded" non ha utility GNU. Più probabilmente quelli di busybox.
Stéphane Chazelas il

-Pè l'impostazione predefinita in find. Se vuoi seguire i symlink, è -L/ -follow. Scoprirai che POSIX non specifica nemmeno quell'opzione per find(anche se POSIX è colui che ha introdotto questi -P / -H / -L per alcuni comandi).
Stéphane Chazelas il

0

Per usare GNU sed puoi usare l' -zopzione, che definisce una linea come stringhe con terminazione zero e cerca ed elimina le righe vuote in questo modo:

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

Il comando head in mezzo è solo un'ottimizzazione.


-1

Pitone

File singolo

Definisci l'alias:

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

Provalo:

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

Più file

Trova ricorsivamente tutti i file binari:

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 {}" \;

Per trovare tutti i file non binari, cambia &&con ||.


1
La domanda ha chiesto di identificare i file che contengono solo (a capo ignorando) caratteri NUL, il codice Python qui dato file identifica contenenti eventuali caratteri NUL.
Tyson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.