Trouver des fichiers épars?

19

Existe-t-il un moyen simple de trouver tous les fichiers épars sur mon système ou dans une arborescence de répertoires particulière?

Si cela est pertinent, j'utilise zshsur Ubuntu 12.04, bien qu'une réponse Unix-y plus générique pour bash / sh, par exemple, conviendrait.

Edit : pour clarifier, je cherche à rechercher des fichiers clairsemés, pas à vérifier l'état de clarté d'un seul.

Andrew Ferrier
la source
2
Qu'est-ce qui vous fait sentir que la recherche de fichiers clairsemés n'implique pas de vérifier l'état de clarté des fichiers individuels?
jlliagre

Réponses:

11

Sur les systèmes (et systèmes de fichiers) prenant en charge l' SEEK_HOLE lseekindicateur (comme le ferait votre Ubuntu 12.04 sur ext4) et supposant que la valeur de SEEK_HOLEest 4 comme sur Linux:

if perl -le 'seek STDIN,0,4;$p=tell STDIN;
   seek STDIN,0,2; exit 1 if $p == tell STDIN'< the-file; then
  echo the-file is sparse
else
  echo the-file is not sparse
fi

Cette syntaxe shell est POSIX. Les trucs non portables sont dedans perlet ça SEEK_HOLE.

lseek(SEEK_HOLE)cherche au début du premier trou dans le fichier, ou à la fin du fichier si aucun trou n'est trouvé. Ci-dessus, nous savons que le fichier n'est pas rare lorsque le lseek(SEEK_HOLE)nous amène à la fin du fichier (au même endroit que lseek(SEEK_END)).

Si vous souhaitez répertorier les fichiers épars:

find . -type f ! -size 0 -exec perl -le 'for(@ARGV){open(A,"<",$_)or
  next;seek A,0,4;$p=tell A;seek A,0,2;print if$p!=tell A;close A}' {} +

Le GNU find(depuis la version 4.3.3) doit -printf %Ssignaler la rareté d'un fichier. Il adopte la même approche que la réponse de frostschutz en ce qu'il prend le rapport entre l'utilisation du disque et la taille du fichier, il n'est donc pas garanti de signaler tous les fichiers épars (comme lorsqu'il y a compression au niveau du système de fichiers ou lorsque l'espace économisé par les trous ne le fait pas). compenser la surcharge de l'infrastructure du système de fichiers ou les grands attributs étendus), mais fonctionnerait sur des systèmes qui n'en ont pas SEEK_HOLEou sur des systèmes de fichiers où il SEEK_HOLEn'est pas implémenté. Ici avec les outils GNU:

find . -type f ! -size 0 -printf '%S:%p\0' |
  awk -v RS='\0' -F : '$1 < 1 {sub(/^[^:]*:/, ""); print}'

(notez qu'une version antérieure de cette réponse ne fonctionnait pas correctement lorsqu'elle findexprimait la rareté comme par exemple 3.2e-05. Merci à la réponse de @ flashydave de l' avoir portée à mon attention)

Stéphane Chazelas
la source
Même commentaire que ci-dessus; Je cherche un moyen de trouver tous les fichiers clairsemés, pas de vérifier un fichier particulier.
Andrew Ferrier
1
Peut-être finddevrait également exclure purement et simplement les fichiers de 0 octet?
frostschutz
@frostschutz, bon point, réponse mise à jour.
Stéphane Chazelas
Belle trouvaille avec le find -printf '%S'! :-)
frostschutz
1
@Brian, remplacez la trcommande parxargs -r0 rm -f
Stéphane Chazelas
8

Un fichier est généralement clairsemé lorsque le nombre de blocs alloués est inférieur à la taille du fichier (en utilisant ici GNU statcomme on le trouve sur Ubuntu, mais attention, d'autres systèmes peuvent avoir des implémentations incompatibles de stat).

if [ "$((`stat -c '%b*%B-%s' -- "$file"`))" -lt 0 ]
then
    echo "$file" is sparse
else
    echo "$file" is not sparse
fi

Variante avec find: (volé à Stéphane)

find . -type f ! -size 0 -exec bash -c '
    for f do
        [ "$((`stat -c "%b*%B-%s" -- "$f"`))" -lt 0 ] && printf "%s\n" "$f";
    done' {} +

Vous placez généralement cela dans un script shell à la place, puis exécutez le script shell.

find . -type f ! -size 0 -exec ./sparsetest.sh {} +
frostschutz
la source
Cela peut ne pas fonctionner si les blocs épars ne sont pas suffisants pour couvrir les frais généraux des blocs indirects dans les systèmes de fichiers traditionnels, par exemple, si la compression au lieu de la rareté réduit la quantité d'espace alloué.
Stéphane Chazelas
Sûr; SEEK_HOLEest tout aussi problématique, car il n'est pas pris en charge par de nombreuses plates-formes / systèmes de fichiers. Sous Linux, vous pouvez également utiliser FIEMAP/ FIBMAP, mais FIBMAPen particulier, c'est horriblement lent ... il ne semble tout simplement pas être un bon moyen.
frostschutz
Beaucoup de ces méthodes nécessitent également que le fichier soit synchronisé en premier.
frostschutz
Merci. Mais cela ne répond pas vraiment à la question. Je ne cherche pas à vérifier si un fichier particulier est clairsemé, mais à trouver tous les fichiers clairsemés du système.
Andrew Ferrier
1
@AndrewFerrier désolé, je suppose que je pensais que c'était assez trivial pour envelopper cela dans un for file in *ou find. Si vous pouvez tester un seul fichier, vous pouvez tester tous les fichiers ... bien que vous deviez exclure les répertoires avec cette méthode.
frostschutz
3

La réponse de Stephane Chazelas ci-dessus ne prend pas en compte le fait que certains fichiers clairsemés avec le paramètre find% S rapportent le rapport sous forme de nombres à virgule flottante comme

9.31323e-09:./somedir/sparsefile.bin

Ceux-ci peuvent être trouvés en plus avec

find . -type f ! -size 0 -printf '%S:%p\0' |
   sed -zn '/^\(0[^:]*:\)\|\([0-9.]\+e-.*:\)/p' |
   tr '\0' '\n'
flashydave
la source
1

Un court script que j'ai écrit en essayant de savoir quels sont les emplacements des trous dans un fichier:

#!/usr/bin/python3
import os
import sys
import errno

def report(fname):
    fd = os.open(fname, os.O_RDONLY)
    len = os.lseek(fd, 0, os.SEEK_END)
    offset = 0
    while offset < len:
        start = os.lseek(fd, offset, os.SEEK_HOLE)
        if start == len:
            break
        try:
            offset = os.lseek(fd, start, os.SEEK_DATA)
        except OSError as e:
            if e.errno == errno.ENXIO:
                offset = len
            else:
                raise
        print(f'found hole between 0x{start:08X} and 0x{offset:08X} ({offset - start} bytes)')

if __name__ == '__main__':
    for name in sys.argv[1:]:
        report(name)

Cela imprime des trucs comme:

$ echo -n 'a' >zeros; truncate -s $((4096*4)) zeros; test/report-holes.py zeros
found hole between 0x00001000 and 0x00004000 (12288 bytes)
zbyszek
la source
Ne répond pas à ma question car je cherchais des fichiers clairsemés, pas les trous dans un fichier spécifique, mais toujours un script utile / pertinent. Merci. A voté.
Andrew Ferrier