2
votes

moyen le plus rapide de faire la somme des tailles de fichier par propriétaire dans un répertoire

J'utilise la commande ci-dessous en utilisant un alias pour imprimer la somme de toutes les tailles de fichiers par propriétaire dans un répertoire

ls -l $dir | awk ' NF>3 { file[$3]+=$5 } \
END { for( i in file) { ss=file[i]; \
if(ss >=1024*1024*1024 ) {size=ss/1024/1024/1024; unit="G"} else \ 
if(ss>=1024*1024) {size=ss/1024/1024; unit="M"} else {size=ss/1024; unit="K"}; \
format="%.2f%s"; res=sprintf(format,size,unit); \
printf "%-8s %12d\t%s\n",res,file[i],i }}' | sort -k2 -nr

mais cela ne semble pas toujours être rapide .

Est-il possible d'obtenir le même résultat d'une autre manière, mais plus rapidement?


6 commentaires

pourquoi ne pas analyser les ls


Vous n'avez pas besoin d'échapper aux nouvelles lignes dans une chaîne.


consultez superuser.com/a/597173


Quand il est lent, à quelle vitesse ls -l $ dir est-il seul? Sur certains systèmes de fichiers, la liste des répertoires volumineux est très, très lente.


@AaronDigulla .. oui le ls -l $ dir est également lent .. il y a plus de 2000 fichiers créés par différents identifiants fonctionnels ..


J'ai environ 308 530 fichiers dans un de ces répertoires.


6 Réponses :


2
votes

Analyse de la sortie de ls - mauvaise idée.

Pourquoi ne pas utiliser find à la place?

  • démarrer dans le répertoire $ {dir}
    • limite à ce niveau de répertoire ( -maxdepth 1 )
    • limite aux fichiers ( -type f )
    • imprimer une ligne avec le nom d'utilisateur et la taille du fichier en octets ( -printf "% u% s \ n" )
  • exécuter les résultats via un filtre Perl
    • diviser chaque ligne ( -a )
    • ajouter à un hachage sous clé (champ 0) la taille (champ 1)
    • à la fin ( END {...} ) affiche le contenu du hachage, trié par clé, c'est-à-dire par nom d'utilisateur
$ perl dummy.pl .
1000 263618544

Une solution utilisant Perl:

#!/usr/bin/perl
use strict;
use warnings;
use autodie;

use File::Spec;

my %users;
foreach my $dir (@ARGV) {
    opendir(my $dh, $dir);

    # files in this directory
    while (my $entry = readdir($dh)) {
        my $file = File::Spec->catfile($dir, $entry);

        # only files
        if (-f $file) {
            my($uid, $size) = (stat($file))[4, 7];
            $users{$uid} += $size
        }
    }

    closedir($dh);
}

print "$_ $users{$_}\n" foreach (sort keys %users);

exit 0;

Exécution du test:

$ find ${dir} -maxdepth 1 -type f -printf "%u %s\n" | \
     perl -ane '$s{$F[0]} += $F[1]; END { print "$_ $s{$_}\n" foreach (sort keys %s); }'
stefanb 263305714


2 commentaires

Il devrait s'imprimer pour tous les propriétaires .. pas seulement pour l'utilisateur actuel .. les fichiers appartiennent à des utilisateurs diff


Mis à jour en conséquence



1
votes

Je ne sais pas pourquoi la question est balisée en perl lorsque awk est utilisé.

Voici une version simple de Perl:

#!/usr/bin/perl

chdir($ARGV[0]) or die("Usage: $0 dir\n");

map {
    if ( ! m/^[.][.]?$/o ) {
        ($s,$u) = (stat)[7,4];
        $h{$u} += $s;
    }
} glob ".* *";

map {
    $s = $h{$_};
    $u = !( $s      >>10) ? ""
       : !(($s>>=10)>>10) ? "k"
       : !(($s>>=10)>>10) ? "M"
       : !(($s>>=10)>>10) ? "G"
       :   ($s>>=10)      ? "T"
       :                    undef
       ;
    printf "%-8s %12d\t%s\n", $s.$u, $h{$_}, getpwuid($_)//$_;
} keys %h;

  • glob obtient notre liste de fichiers
  • m // rejette . et ..
  • stat la taille et l'uid
  • accumuler des tailles dans %h
  • calculer l'unité par décalage de bits ( >> 10 est une division entière par 1024)
  • mapper l'uid au nom d'utilisateur ( // fournit une solution de secours)
  • résultats d'impression (non triés)
  • REMARQUE: contrairement à certaines autres réponses, ce code ne se répète pas dans les sous-répertoires

Pour exclure les liens symboliques, les sous-répertoires, etc., changez le if en tests -X appropriés. (par exemple. (-f $ _) , (! -d $ _ et! -l $ _) , etc.). Voir documentation perl sur l'optimisation du gestionnaire de fichiers _ pour la mise en cache résultats statistiques.


3 commentaires

Je ne vois pas m /// dans le script. Je suppose que vous faites référence à ! / ^ [.] [.]? $ / O ?


Oui. // est le raccourci pour m // . m n'est nécessaire que si vous souhaitez utiliser un délimiteur différent (par exemple m [] , m<> , etc.). Trois barres obliques étaient une faute de frappe.


Veuillez utiliser m // dans le script ou utiliser le code du script dans l'explication. Dans l'état actuel des choses, c'est très déroutant pour les gens qui ne connaissent pas grand chose de Perl.



4
votes

Un autre perl, qui affiche les tailles totales triées par utilisateur:

#!/usr/bin/perl
use warnings;
use strict;
use autodie;
use feature qw/say/;
use File::Spec;
use Fcntl qw/:mode/;

my $dir = shift;
my %users;

opendir(my $d, $dir);
while (my $file = readdir $d) {
  my $filename = File::Spec->catfile($dir, $file);
  my ($mode, $uid, $size) = (stat $filename)[2, 4, 7];
  $users{$uid} += $size if S_ISREG($mode);
}
closedir $d;

my @sizes = sort { $a->[0] cmp $b->[0] }
  map { [ getpwuid($_) // $_, $users{$_} ] } keys %users;
local $, = "\t";
say @$_ for @sizes;


1 commentaires

@ stack0114106 Il limite le suivi de la taille aux fichiers normaux - saute les répertoires, les fifos, les sockets, les périphériques, etc. Même idée que le -f $ file dans une autre réponse, juste une manière différente de vérifier.



2
votes

7 commentaires

ressemble à mon awk n'a pas de filefuncs awk: foo.awk: 1: ^ caractère invalide '@' dans l'expression


Il est temps de passer à une version moderne de GNU awk.


c'est dans Enterprise Linux - RHEL 6.10 .. Je vois gawk pointant vers / bin / gawk et la version est GNU Awk 3.1.7 .. prend-il en charge @loadfiles? .. ou y a-t-il un autre emplacement qui aurait un autre awk? ? ..


Une supposition sauvage que les extensions sont venues dans GNU awk 4. Mais j'ai vu que vous avez mentionné 300k fichiers, cette solution ne peut pas en gérer autant.


ok .. de toute façon bon de savoir loadfiles ... J'ai exécuté ceci dans mon cygwin et ça marche..so ++


Eh bien, cette réécriture peut gérer au moins 100k fichiers (testés).


continuons cette discussion dans le chat .



2
votes

Obtenez une liste, additionnez les tailles et triez-la par propriétaire (avec Perl)

use warnings;
use strict;
use feature 'say';    
use File::Find;
use Getopt::Long;

my %rept;    
sub get_sizes {
    return if not -f; 
    my ($owner_id, $size) = (stat)[4,7] 
        or do { warn "Trouble stat for: $_"; return };
    $rept{$owner_id} += $size 
}

my ($dir, $recurse) = ('.', '');
GetOptions('recursive|r!' => \$recurse, 'directory|d=s' => \$dir)
    or die "Usage: $0 [--recursive] [--directory dirname]\n";

($recurse) 
    ? find( { wanted => \&get_sizes }, $dir )
    : find( { wanted => \&get_sizes, 
              preprocess => sub { return grep { -f } @_ } }, $dir );

say (getpwuid($_)//$_, " => $rept{$_} bytes") for keys %rept;

Je n'ai pas pu la comparer, et ça vaudrait la peine de l'essayer une approche où le répertoire est itéré sur, par opposition à glob -ed (alors que j'ai trouvé glob beaucoup plus rapide dans un problème lié ).

Je m'attends à de bons temps d'exécution par rapport à ls , qui ralentit considérablement comme une liste de fichiers dans un seul répertoire devient longue. Cela est dû au système, donc Perl sera également affecté, mais pour autant que je me souvienne, il le gère beaucoup mieux. Cependant, j'ai vu un ralentissement dramatique seulement une fois que les entrées atteignent environ un demi-million, pas quelques milliers, donc je ne sais pas pourquoi cela fonctionne lentement sur votre système.

Si cela doit être récursif dans les répertoires qu'il trouve, utilisez File :: Find . Par exemple

perl -MFile::Find -wE'
    $dir = shift // "."; 
    find( sub { 
        return if not -f;
        ($owner_id, $size) = (stat)[4,7] 
            or do { warn "Trouble stat for: $_"; return }; 
        $rept{$owner_id} += $size 
    }, $dir ); 
    say (getpwuid($_)//$_, "$_ => $rept{$_} bytes") for keys %rept
'

Cela scanne un répertoire avec 2,4 Go, principalement de petits fichiers sur une hiérarchie de sous-répertoires, en un peu plus de 2 secondes. Le du -sh a pris environ 5 secondes (la première fois).


Il est raisonnable de réunir ces deux dans un seul script

perl -wE'
    chdir (shift // "."); 
    for (glob ".* *") { 
        next if not -f;
        ($owner_id, $size) = (stat)[4,7]
            or do { warn "Trouble stat for: $_"; next };
        $rept{$owner_id} += $size 
    } 
    say (getpwuid($_)//$_, " => $rept{$_} bytes") for sort keys %rept
'

Je trouve que cela fonctionne à peu près de la même manière que le code à un répertoire ci-dessus, lorsqu'il est exécuté de manière non récursive (par défaut tel quel).

Notez que L'interface File :: Find :: Rule présente de nombreuses commodités mais est plus lent dans certains cas d'utilisation importants, ce qui compte clairement ici. (Cette analyse devrait être refaite car elle a quelques années.)


12 commentaires

ainsi que getpwuid ne renvoyant peut-être rien (et fusionnant ainsi des uids distincts), si vous l'invoquez dans le sous-répertoire find, vous l'appelez une fois par fichier, contre une fois par uid si vous le faites pendant le say.


@jhnc Oui, sur les deux: (1) vient d'ajouter la gestion des erreurs, (2) Je ne suis pas concerné par un appel système supplémentaire en cours de traitement (extraire la liste du système est lent) et je voulais rassembler des noms, mais oui, ce serait être plus rapide (et probablement généralement préférable de le conserver tel que renvoyé par stat )


@ stack0114106 Ah! Cela devait donc concerner les utilisateurs qui ont été supprimés (ou autres), donc getwpuid n'a rien renvoyé ( undef ) --- un rappel à toujours, en effet < / i> inclut tous les tests requis !!! (Je ne vois toujours pas pourquoi les impressions de débogage ont échoué avec l'avertissement " valeur non initialisée $owner_id ")


@zdim J'ai créé un dossier avec 200k fichiers et exécuté votre code avec getpwuid dans pour , puis déplacé vers say . Le premier a pris 2,456s / 1,063s / 1,369s, le second a pris 0,862s / 0,347s / 0,515s. Ces appels supplémentaires s'additionnent! (sur un SSD au moins ...) :-)


btw, je pense que votre version de recherche a une faute de frappe dans l'expression rationnelle - devrait être /^\.\.?$/ ou similaire


@jhnc aïe, il faut absolument être \. dans regex - merci! Curieusement, cela a fonctionné dans mes tests car il exclut uniquement les entrées avec un ou deux caractères dans le nom, et les seuls que j'ai sont en effet . et .. :). J'y reviendrai dès que possible et j'essaierai maintenant d'optimiser. Merci de le chronométrer - je soupçonne que ce sera toujours (plus ou moins) le même; de sorte que lorsque le traitement tombe à quelques minutes (pour des centaines de milliers de fichiers dans un répertoire), nous avons encore de l'ordre d'une seconde de retard à cause de ces appels système. Je vais chronométrer :)


@ stack0114106 Modification du code pour appeler getpwuid uniquement lors de l'impression du rapport. C'est généralement mieux, et cela semble avoir un effet important, augmentant avec le nombre de fichiers (?!). Je suppose que j'ai mal évalué cela. Plus à venir...


@ stack0114106 Par " répertoire courant " voulez-vous dire sans récursivité? Comme avec un indicateur de ligne de commande, soit ne faire que le répertoire actuel ou tout faire avec la récursivité?


@zdim .. oui .. bien .. comme une solution est l'extension de l'autre avec quelques changements de drapeaux


@zdim .. a nettoyé le mien .. :-)


@ stack0114106 Modifié. En plus d'ajouter un seul script pour les deux, j'ai aussi légèrement modifié le script récursif: il incluait la taille des répertoires, ce que je pensais que vous ne vouliez probablement pas ici. Si vous le faites, faites-le moi savoir et je rétablirai le comportement précédent.


@zdim .. merci beaucoup pour la consolidation..oui, les tailles des répertoires peuvent être omises ..



0
votes

Utilisation de datamash (et Stefan Becker find code ):

find ${dir} -maxdepth 1 -type f -printf "%u\t%s\n" | datamash -sg 1 sum 2


2 commentaires

@ agc..la réponse semble simple .. est-ce que le datamash est disponible dans RHEL 6.1?


@ stack0114106, Incertain - Des fichiers RPM existent , mais si ceux-ci fonctionnent dans RHEL 6.1 n'est pas clair sans une boîte 6.1 pour tester.