Comment trouver des fichiers avec des caractères 100% NUL dans leur contenu?

16

Quelle est la commande de ligne de commande Linux qui peut identifier de tels fichiers?

AFAIK la findcommande (ou grep) ne peut correspondre qu'à une chaîne spécifique à l'intérieur du fichier texte. Mais je veux faire correspondre le contenu entier, c'est-à-dire que je veux voir quels fichiers correspondent à l'expression régulière \0+, en ignorant les caractères de fin de ligne . Peut-être que l' find . cat | grepidiome pourrait fonctionner, mais je ne sais pas comment faire grep en ignorant les lignes (et traiter le fichier comme binaire).

Contexte: Tous les quelques jours, lorsque mon ordinateur portable se bloque, ma partition btrfs perd des informations: les fichiers ouverts en écriture voient leur contenu remplacé par des zéros (la taille du fichier reste plus ou moins intacte). J'utilise la synchronisation et je ne veux pas que ces faux fichiers se propagent: j'ai besoin d'un moyen de les identifier afin de pouvoir les récupérer depuis la sauvegarde.

Adam Ryczkowski
la source
vous voulez dire des fichiers contenant des zéros numériques?
Rahul Patil
2
Je pense qu'il s'agit de caractères NULL plutôt que de zéros numériques.
gertvdijk
10
Revenons en arrière ici. Tous les quelques jours, lorsque votre ordinateur portable se bloque? Pourquoi n'essayons-nous pas de résoudre ce problème , le vrai problème ici?
D_Bye
2
@D_Bye c'est une bonne idée, mais jusqu'à présent, cela n'est pas allé
Adam Ryczkowski
1
avez-vous envisagé l' -voption de grep: filtrer tous les fichiers qui ont des octets de 1 à 255.
ctrl-alt-delor

Réponses:

10

Vous pouvez greppour ␀ caractères en utilisant le mode 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

Vous pouvez donc utiliser ceci:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done
l0b0
la source
J'obtiens des résultats inattendus en utilisant GNU grep 2.5.4. Peu importe si j'utilise --binary-files=textou --binary-files=binary, cela donne un truerésultat pour toutes les valeurs de données non vides, par exemple. "\0\0","\0x\0" , "abcd"... Le code exact j'ai utilisé est: 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
J'ai maintenant essayé GNU grep) 2.10 . Cette version ultérieure donne les résultats escomptés ... alors, un +1 tardif
Peter.O
1
Échoue sur un fichier créé avec printf '\0\n\0\0\n\n' > file ou printf '\n' > filepour ce qui compte.
Stéphane Chazelas
2
@ StéphaneChazelas OP a dit "ignorer les caractères de fin de ligne". Donc, tout fichier composé uniquement\0 et de \ncaractères (même zéro de l'un ou l'autre) serait une correspondance.
l0b0
6

Je suis d'accord avec ce que D_Bye dit sur la recherche de la racine du problème.

Quoi qu'il en soit pour vérifier si un fichier contient uniquement \0et / ou \nvous pouvez utiliser tr:

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

Qui renvoie 0 pour les fichiers null / newline et vides.

Thor
la source
2
tr -d '\0\n'résout le problème de nouvelle ligne, qui ne laisse alors que le problème (?) des fichiers vides répertoriés dans la sortie ... Il traite cependant chaque octet de chaque fichier (qui peut ou non être un problème) +1
Peter.O
@ Peter.O: J'ai manqué l'exigence de la nouvelle ligne, merci. Cette solution n'est pas très optimisée et si elle doit fonctionner sur un grand nombre de données, ce serait mieux avec une solution qui passe à la recherche d'octets non correspondants.
Thor
Il fonctionne très bien. Dans mon cas, je n'avais qu'à m'assurer d'exclure les fichiers de longueur nulle. Je vous remercie.
Adam Ryczkowski
1
Cependant, cela comptera également les fichiers avec des retours à la ligne comme étant "vides".
Chris Down
1
@ChrisDown: J'ai expliqué clairement le texte de la réponse. On ne sait pas exactement ce que l'OP veut faire avec les fichiers de nouvelle ligne uniquement.
Thor
5

Je soupçonne que ces fichiers sont rares, c'est-à-dire qu'ils n'ont pas d'espace disque alloué, ils spécifient simplement une taille de fichier ( durapporterait 0 pour eux).

Dans ce cas, avec GNU find, vous pouvez faire (en supposant qu'aucun chemin de fichier ne contient de caractères de nouvelle ligne):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-
Stéphane Chazelas
la source
Bon point. Je n'avais jamais pensé à ça. J'essaierai. L'utilisation duempêchera de rayer le contenu de chaque fichier du système de fichiers, de sorte que toute la procédure ne prendrait pas plus de 30 minutes.
Adam Ryczkowski
(et printf %bci - dessus rapporte ce duqui rapporterait)
Stéphane Chazelas
Je changerais -size +0pour -size +1que les fichiers de longueur nulle soient exclus des résultats. De plus, les fichiers contenant \ndans leur chemin entraîneront des problèmes pour cette commande.
Tyson
@Tyson -size +0est pour des tailles strictement supérieures à 0. -size +1serait pour des tailles strictement supérieures à 512. La limitation de la nouvelle ligne a déjà été mentionnée.
Stéphane Chazelas
@ StéphaneChazelas Merci de m'avoir éclairé -size +1, vous avez en effet raison. J'ai corrigé ma réponse. :-)
Tyson
4

Voici un petit programme python qui peut le faire:

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

Et en action:

$ 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

Vous pouvez vérifier plusieurs fichiers en utilisant FIND -exec, xargs, GNU parallel, et des programmes similaires. Alternativement, cela imprimera les noms de fichiers qui doivent être traités:

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

Gardez à l'esprit que si vous allez transmettre la sortie de ceci à un autre programme, les noms de fichiers peuvent contenir des sauts de ligne, vous devez donc les délimiter différemment (convenablement, avec \0).

Si vous avez beaucoup de fichiers, il serait préférable d'utiliser une option pour le traitement parallèle, car elle ne lit qu'un fichier à la fois.

Chris Down
la source
2
Méfiez - vous, fichiers vides (par exemple: /etc/nologin, ~/.hushlogin, .nomedia, ...) sont mal identifiés par cette réponse.
Tyson
@Tyson Merci de l'avoir signalé! Je viens de le réparer.
Chris Down
3

Recherchez les fichiers qui contiennent uniquement des caractères nuls '\ 0' et des caractères de nouvelle ligne '\ n'.
Le qdans sed fait que chaque fichier recherche de quitter immédiatement à trouver un caractère non nul en ligne.

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

Créer des fichiers de 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

production

./file-with-nulls-and-newlines
./file-with-nulls-only
Peter.O
la source
Soit l' -print0argument semble manquer, findsoit la IFS=pièce est foirée. Quel était le délimiteur prévu?
Tyson
3

Cette ligne unique est le moyen le plus efficace de trouver 100% des fichiers NUL en utilisant GNU find, xargset grep( en supposant que ce dernier est construit avec le soutien PCRE):

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

Les avantages de cette méthode par rapport aux autres réponses fournies sont:

  • les fichiers non clairsemés sont inclus dans la recherche.
  • les fichiers non lisibles ne sont pas transmis à grep, ce qui évite les Permission deniedavertissements.
  • greparrêtera la lecture des données des fichiers après avoir trouvé un octet non nul ( LC_ALL=Cest utilisé pour s'assurer que chaque octet est interprété comme un caractère ).
  • les fichiers vides (zéro octet) ne sont pas inclus dans les résultats.
  • moins de grepprocessus vérifient efficacement plusieurs fichiers.
  • les chemins contenant des sauts de ligne ou commençant par -sont traités correctement.
  • fonctionne sur la plupart des systèmes embarqués qui manquent de Python / Perl.

Passer l' -Zoption grepet l'utiliser xargs -r0 ...permet d'effectuer d'autres actions sur les fichiers 100% nul (ex: nettoyage):

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

Je recommande également d'utiliser les findoptions -Ppour éviter de suivre les liens symboliques et -xdevpour éviter de traverser les systèmes de fichiers (par exemple: montages distants, arborescences de périphériques, montages de liaison, etc.).

Pour ignorer les caractères de fin de ligne , la variante suivante devrait fonctionner (bien que je ne pense pas que ce soit une si bonne idée):

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

Tout rassembler, y compris la suppression des fichiers indésirables (caractères 100% nul / newline) pour les empêcher d'être sauvegardés:

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

Je ne recommande pas d'inclure des fichiers vides (zéro octet), ils existent souvent à des fins très spécifiques .

Tyson
la source
Être le plus rapide parmi tant d'autres alternatives est une affirmation audacieuse. Je marquerai votre réponse comme acceptée si vous ajoutez un point de repère :-)
Adam Ryczkowski
Une telle référence dépendrait de nombreux facteurs, notamment les performances des différents sous-systèmes de disques.
Tyson
Bien sûr, mais tout vaut mieux que rien. Différentes approches optimisent différemment l'utilisation du processeur, il est donc logique de le comparer sur SSD ou même sur des fichiers mis en cache. Prenez la machine sur laquelle vous travaillez actuellement, écrivez une phrase (type de processeur, nombre de cœurs, RAM, type de disque dur), décrivez l'ensemble de fichiers (par exemple, clone source du noyau + 1 Go de fichier plein \0avec 900 Mo de trou dedans) et moment actuel des résultats. Si vous le faites d'une manière que l'indice de référence vous convainque, il conviendra probablement à nous tous
Adam Ryczkowski
"la plupart des systèmes embarqués" n'ont pas d'utilitaires GNU. Ceux qui sont probablement occupés.
Stéphane Chazelas
-Pest la valeur par défaut dans find. Si vous voulez suivre les liens symboliques, c'est -L/ -follow. Vous constaterez que POSIX ne spécifie même pas cette option pour find(même si POSIX est celui qui a introduit ces -P / -H / -L pour quelques commandes).
Stéphane Chazelas
0

Pour utiliser GNU sed, vous pouvez utiliser l' -zoption, qui définit une ligne comme des chaînes terminées par zéro et correspond et supprime les lignes vides comme suit:

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

La commande head entre les deux n'est qu'une optimisation.

mxmlnkn
la source
-1

Python

Un seul fichier

Définissez l'alias:

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

Essaye-le:

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

Fichiers multiples

Recherchez tous les fichiers binaires de manière récursive:

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

Pour rechercher tous les fichiers non binaires, modifiez &&avec ||.

Kenorb
la source
1
La question demandait d'identifier les fichiers contenant uniquement des caractères nuls (en ignorant les retours à la ligne), le code Python donné ici identifie les fichiers contenant des caractères nuls.
Tyson