This tool builds a tree of directorys and files and for each entry in the tree shows stats for the entry.

For subdirectorys the number of nested directorys (if any) are shown with the file count and the total size of the files within the directory are shown.

For files the file size is shown.

It would be trivial to show other information for each node.

use warnings; use strict; use Tk; use Tk::Tree; my $rootPath = shift; ShowHelp () if ! defined $rootPath; ShowHelp (-2, "Error finding folder $rootPath\n\n") if ! -d $rootPath; my $main = MainWindow->new (-title => "Folder stats for $rootPath"); my $tree = $main->ScrlTree ( -font => 'FixedSys 8', -itemtype => 'text', -separator => '/', -scrollbars => "osoe" ); my @pathStack = (1); my $maxNesting = 0; my $totalLines; my $maxLineLength = 0; my $currPath = join "/", @pathStack; $tree->add ($currPath, -text => $rootPath); my ($subDirCount, $subFileCount, $subTotalSize) = buildSubTree ($tree, $rootPath, \@pathStack, \$maxNesting, \$totalLines, \$maxLin +eLength); my $annotation = $rootPath . " \t("; my $plural = $subDirCount != 1 ? 's' : ''; $annotation .= $subDirCount . " dir$plural, " if $subDirCount; $annotation .= "$subFileCount files, $subTotalSize bytes)"; $tree->entryconfigure ($currPath, -text => $annotation); $totalLines = 40 if $totalLines > 40; $main->geometry (($maxLineLength + @pathStack * 4) * 5 . 'x' . (40 + $ +totalLines * 20)); closeTree ($tree, ''); $tree->pack(-fill=>'both',-expand => 1); MainLoop; sub closeTree { my $tree = shift; my ($entryPath, $hideChildren) = @_; my @children = $tree->info (children => $entryPath); return if ! @children; for (@children) { closeTree ($tree, $_, 1); $tree->hide ('entry' => $_) if $hideChildren; } $tree->setmode ($entryPath, 'open') if length $entryPath; } sub buildSubTree { my ($tree, $rootPath, $pathStack, $maxNesting, $totalLines, $maxLine +Length) = @_; my $dirCount = 0; my $fileCount = 0; my $sizeTotal = 0; push @$pathStack, 1; $$maxNesting = @$pathStack if $$maxNesting < @$pathStack; my $dir; opendir $dir, $rootPath; while (my $currDir = readdir $dir) { next if $currDir =~ /^\.\.?$/; my $path = "$rootPath/$currDir"; ++$$totalLines; my $currPath = join "/", @$pathStack; my $nodeText = $path; $tree->add ($currPath, -text => $nodeText); if (-d $path) { my ($subDirCount, $subFileCount, $subTotalSize) = buildSubTree ($tree, $path, $pathStack, $maxNesting, $totalLines, $maxLineL +ength); $dirCount += $subDirCount + 1; $fileCount += $subFileCount; $sizeTotal += $subTotalSize; my $annotation = $nodeText . " \t("; my $plural = $subDirCount != 1 ? 's' : ''; $annotation .= $subDirCount . " dir$plural, " if $subDirCount; $annotation .= "$subFileCount files, $subTotalSize bytes)"; $tree->entryconfigure ($currPath, -text => $annotation); } else { my $fileSize = -s $path; $tree->entryconfigure ($currPath, -text => $nodeText . " ($fileS +ize bytes)"); ++$fileCount; $sizeTotal += $fileSize; } $$maxLineLength = length ($nodeText) if length ($nodeText) > $$max +LineLength; ++$pathStack->[-1]; } closedir $dir; pop @$pathStack; return ($dirCount, $fileCount, $sizeTotal); } sub ShowHelp { my $exitValue = 0; $exitValue = shift if defined $_[0] and $_[0] =~ /^[-+]?\d+$/; print $_ while $_ = shift; print <<HELP; FolderStats scans a directory tree starting at the folder given on the + command line and generates an explorer like tree giving folder content stats s +uch as number of files, their total size, and the count and sizes of sub-fold +ers. Note that the statistics are not dynamically updated as files and fold +ers are altered on disk. Usage: FolderStats <root folder> HELP exit ($exitValue || -1); }

DWIM is Perl's answer to Gödel

Replies are listed 'Best First'.
Re: Directory tree explorer with stats reporting
by parv (Parson) on Mar 11, 2006 at 02:46 UTC

    Update (after a day): Please see my another reply (20.24 Mar 11 2006 (EST) for even more updated code.

    Below is the patch (generated after running perltidy on original code & the new code; there still one more location where size comes out to be undef due to circular/unresolved symbolic link which i could not locate) ...

    • skips a directory (as identified by -d) which is also a symbolic link (identified by -l);
    • refactors the creation of $annotation;
    • converts the size to kilobytes;
    • comments out the -font setting as FixedSys font is missing here and the substituted fonts turns out to be too tiny;
    • enables the always-on scrollbars (as scrollbars-only-when-needed-option, 'o', was not working here);

    --- 535607.pl.tdy Fri Mar 10 21:16:23 2006 +++ dir-explore.pl Fri Mar 10 21:43:49 2006 @@ -1,3 +1,5 @@ +#!perl + use warnings; use strict; use Tk; @@ -10,10 +12,14 @@ my $main = MainWindow->new( -title => "Folder stats for $rootPath" ); my $tree = $main->ScrlTree( - -font => 'FixedSys 8', - -itemtype => 'text', - -separator => '/', - -scrollbars => "osoe" + #-font => 'FixedSys 8', + -itemtype => 'text', + -separator => '/', + + # Having scrollbars-only-when-needed-option, 'o', does not make +the + # scrollbars appear when the content overflows display area (Fre +eBSD + # 6-STABLE & Tk-804.027). + -scrollbars => 'sw' ); my @pathStack = (1); @@ -26,11 +32,9 @@ my ( $subDirCount, $subFileCount, $subTotalSize ) = buildSubTree( $tree, $rootPath, \@pathStack, \$maxNesting, \$totalL +ines, \$maxLineLength ); -my $annotation = $rootPath . " \t("; -my $plural = $subDirCount != 1 ? 's' : ''; -$annotation .= $subDirCount . " dir$plural, " if $subDirCount; -$annotation .= "$subFileCount files, $subTotalSize bytes)"; -$tree->entryconfigure( $currPath, -text => $annotation ); +$tree->entryconfigure( $currPath, + -text => annotate( $rootPath, $subDirCount, $subFileCount, $subTo +talSize ) +); $totalLines = 40 if $totalLines > 40; $main->geometry( @@ -79,7 +83,7 @@ $tree->add( $currPath, -text => $nodeText ); - if ( -d $path ) { + if ( -d $path && !-l $path ) { my ( $subDirCount, $subFileCount, $subTotalSize ) = buildSubTree( $tree, $path, $pathStack, $maxNesting, $t +otalLines, $maxLineLength ); @@ -88,17 +92,19 @@ $fileCount += $subFileCount; $sizeTotal += $subTotalSize; - my $annotation = $nodeText . " \t("; - my $plural = $subDirCount != 1 ? 's' : ''; - $annotation .= $subDirCount . " dir$plural, " if $subDirC +ount; - $annotation .= "$subFileCount files, $subTotalSize bytes) +"; - $tree->entryconfigure( $currPath, -text => $annotation ); + $tree->entryconfigure( + $currPath, + -text => annotate( + $nodeText, $subDirCount, $subFileCount, $subTotal +Size + ) + ); } else { my $fileSize = -s $path; $tree->entryconfigure( $currPath, - -text => $nodeText . " ($fileSize bytes)" ); + -text => $nodeText . ' (' . size_in_kilobyte($fileSiz +e) . ')' ); ++$fileCount; +#warn $path unless defined $fileSize; $sizeTotal += $fileSize; } @@ -110,6 +116,25 @@ closedir $dir; pop @$pathStack; return ( $dirCount, $fileCount, $sizeTotal ); +} + +sub annotate { + my ( $path, $dirs, $files, $byte_size ) = @_; + return + $path + . " \t(" + . $dirs . ' dir' . count_to_plural_suffix($dirs) . ', ' + . $files . ' file' . count_to_plural_suffix($files) . ', ' + . size_in_kilobyte($byte_size) + . ')'; +} + +sub count_to_plural_suffix { + return $_[0] > 1 ? 's' : ''; +} + +sub size_in_kilobyte { + defined $_[0] ? sprintf( '%0.1f', $_[0] / 1024 ) . ' kB' : 'UNKNO +WN SIZE'; } sub ShowHelp {

    Updated program follows ...

      there still one more location where size comes out to be undef due to circular/unresolved symbolic link which i could not locate

      Well, i do see the problem now; it would happen while calculating the size of $path in the alternate branch ...

      if ( -d $path && !-l $path ) { ... } else { my $fileSize = -s $path; ... $sizeTotal += $fileSize; }

      The -s function causes chase of symlink via stat which brings back unresolved link which gives undef to $fileSize. So when undef is added to $sizeTotal, we get the previously mentioned "uninitialized" error message.

Re: Directory tree explorer with stats reporting
by parv (Parson) on Mar 12, 2006 at 01:24 UTC

    Updated code follows to use File::Spec especially for those who are having problem on non-Unix operating systems; please test & report. Additionally, it binds 'q' to quit the Tk window, and displays the size in more appropriate units (see the BEGIN block).

    Update (some minutes later): Explicitly use lstat to avoid the issue of circular|dangling links. It also reduces an extra stat call in alternate branch of if ( -d _ && ! -l _ ) { ... } else{ ... }.

    Update (a few hours later): Move $tree->entryconfigure() outside of if ... else; suppress file & directory display if respective count is 0; removed variables which were used to calculate geometry of the window (which was dependent on font used|available and screen resolution (i think)).

    Update (a few hours & some minutes later): Sort the enteries; move path stack joining in its own function; make the recursion, in buildSubTree(), to be in alternate branch of if ... else.

    Update (next day's evening): Add key binding (enter|return key) to toggle collpase|expansion of a subtree; initially expand the tree of given directory w/ children collapsed; plus some otherwise minor changes.

    Sep 19 2008 Update: Added key binding in help text.

    #!perl # This is a modified copy of the directory explorer code posted on # PerlMonks by 'GrandFather' at ... # # http://perlmonks.org/index.pl?node_id=535607 # http://perlmonks.org/index.pl?displaytype=print;node_id=535607;repl +ies=1 use warnings; use strict; use Tk; use Tk::Tree; use Tk::Font; use File::Spec; BEGIN { # Tk-related variable(s). our ( $node_sep ) = ( '/' ); sub stack_as_string { return join $node_sep , ref $_[0] ? @{ $_[0] } : @_; } # Number of bytes. my %units = ( 1 => 'B' , 1024 => 'KB' , 1024 * 1024 => 'MB' , 1024 * 1024 * 1024 => 'GB' , 1024 * 1024 * 1024 * 1024 => 'TB' ) ; my @ordered = sort { $a <=> $b } keys %units; # Convert size in bytes to a unit (upto TB) appropriate for the ord +er of # the size. sub size_in_xbyte { my ( $size ) = @_; return 'UNKNOWN SIZE' unless defined $size; my $factor = $ordered[0]; foreach my $u ( @ordered ) { $size < $u and last; $factor = $u; } # Use number of bytes (B) as is (knowing that $factor will be 1). my $format = $factor != $ordered[0] ? '%0.1f %s' : '%0d %s'; return sprintf $format, $size / $factor, $units{$factor}; } } our ( $node_sep ); my $tree_start = 1; my $path = shift; ShowHelp() unless defined $path; # In File::Spec pod, there is no constructor method noted, nor is 'si +mple # use' defined for which functional forms of methods are available. +Missing # also is a list of exported function. my $fspec = 'File::Spec'; $path = $fspec->canonpath($path); ShowHelp( -2 , "Error finding folder $path\n\n" ) unless -d $path; my $main = MainWindow->new( '-title' => "Folder stats for $path" ); # Adjust as you like. $main->geometry( '900x700' ); my $tree = $main->ScrlTree ( '-font' => $main->Font( 'systemfont' ) , '-itemtype' => 'text' , '-separator' => $node_sep # Once, having scrollbars-only-when-needed-option, 'o', did not m +ake the # scrollbars appear when the content overflows display area (Free +BSD # 6-STABLE & Tk-804.027, Tk 8.4.11.2). Now (Sun Mar 12 13:18:18 U +TC 2006) # optional scrollbars are behaving as advertised. And i do not k +now # why|how! , '-scrollbars' => 'osow' ) ->pack( '-fill' => 'both' , '-expand' => 1 ) ; my @node_stack = ($tree_start); my $node = $tree_start; $tree->add( $node , '-text' => $path ); my ( $subDirCount , $subFileCount , $subTotalSize ) = buildSubTree( $tree , $path , \@node_stack ); $tree->entryconfigure ( $node , '-text' => annotate( $path , $subTotalSize , $subDirCount , $subFile +Count ) ); $tree->autosetmode; collapse_nodes( $tree ); $tree->open( $node ); $main->bind( '<KeyPress-q>' , sub { exit ; } ); $tree->bind( '<KeyPress-Return>' , \&toggle_tree_expand ); $tree->focus; MainLoop; # Key binding function to expand|collapse a subtree. sub toggle_tree_expand { my $root = $tree->info( 'selection' ); return unless defined $root && length $root; my @nodes = $tree->info( 'children' , $root ); return unless scalar @nodes; # $tree->open() does not (sometimes) expands a tree, but $tree->clo +se() does # collapse an open()'d tree. # -- # Handle both open & close cases for now. my ( $meth ); foreach ( @nodes ) { $meth = $tree->info( 'hidden' , $nodes[-1] ) ? 'show' : 'hide'; $tree->setmode( $root , $meth eq 'show' ? 'close' : 'open' ); $tree->$meth( 'entry' , $_ ); } $tree->update; } sub collapse_nodes { my ( $tree , $node , $collapse ) = @_; my @subnodes = $tree->info( 'children' , $node ); return unless scalar @subnodes; foreach my $n ( @subnodes ) { collapse_nodes( $tree , $n , 1 ); $tree->hide( 'entry' , $n ) if $collapse; } $tree->setmode( $node , 'open' ) if $node; } sub buildSubTree { my ( $tree , $path , $stack ) = @_ ; my ( $dirCount , $fileCount , $sizeTotal ) = (0) x3 ; push @$stack , $tree_start; opendir my $DH , $path or die "Cannot open $path: $!\n"; foreach my $dir ( sort readdir $DH ) { next if $dir eq $fspec->updir or $dir eq $fspec->curdir; # Don't know what will happen if catfile() is substitued w/ catdi +r() # on non-Unix operating systems. my $path = $fspec->catfile( $path , $dir ); my $node = stack_as_string( $stack ); $tree->add( $node , '-text' => $path ); my ( $size , $dirs , $files ); # Use lstat to avoid chasing circular|dangling symbolic links. lstat $path; if ( ! -d _ ) { ++$fileCount; $size = -s _; $sizeTotal += $size if defined $size; } else { ( $dirs , $files , $size ) = buildSubTree( $tree , $path , $stac +k ); $dirCount += $dirs + 1; $fileCount += $files; $sizeTotal += $size; } $tree->entryconfigure ( $node , '-text' => annotate( $path , $size , $dirs , $files ) ); ++$stack->[-1]; } closedir $DH or die "Cannot close $path: $!\n"; pop @$stack; return ( $dirCount , $fileCount , $sizeTotal ); } sub annotate { my ( $path , $size , $dirs , $files ) = @_; return join q// , $path , "\t(" , size_in_xbyte( $size ) , ( map { !$_->[0] ? () : sprintf ', %s %s%s' , @{$_} , count_to_plural_suffix( $_-> +[0] ) } [ $dirs , 'dir' ] , [ $files , 'file' ] ) , ')' ; } sub count_to_plural_suffix { return $_[0] > 1 ? 's' : ''; } sub ShowHelp { my $exitValue = 0; $exitValue = shift if defined $_[0] and $_[0] =~ /^[-+]?\d+$/; print $_ while $_ = shift; print <<HELP; FolderStats scans a directory tree starting at the folder given on the command line and generates an explorer like tree giving folder content stats such as number of files, their total size, and the count and sizes of sub-folders. Note that the statistics are not dynamically updated as files and folders are altered on disk. Usage: FolderStats <root folder> Key Binding: q: quits the program. Enter: expands or collapses a tree. Up, Down: Vertically move up or down. arrows: Left, Right: Move one level up or down. arrows: HELP exit( $exitValue || -1 ); }

      Thanks for the improvements. A couple of minor things:

      count_to_plural_suffix would be better return $_[0] != 1 ? 's' : ''; In English - 0 apples, 1 apple, many apples.

      The new display size is nice for general purpose, but for the application that catalysed the code I needed the exact number of bytes. Just shows, you can't write code to suit everyone. :)


      DWIM is Perl's answer to Gödel

        I personally like seeing "0 X" than to "0 Xs" where things are being counted like in this case. (Or, just do away w/ the count_to_plural_suffix). But as you said, "you can't...".

        I have been myself in situation where i wanted to see the exact number of bytes (or rather in 512-blocks), so no argument there. (I suppose "more appropriate units" caused your response?:)

        This is my first experience w/ Tk, otherwise i would have added some more key bindings (namely rebuilding the tree on request) and options to (interactively) modify the display. Slowly, but surely, one day ...

Re: Directory tree explorer with stats reporting
by blogical (Pilgrim) on Mar 10, 2006 at 19:31 UTC
    I tell it "c:" and it lists local files (my desktop) but claims to be examining c:. It can't find the local files at c:/, so it doesn't tell the bytes and warns about $path being undef on STDERR.
    './' works though.

      I've only tested it on Windows XP so, although in principle it should mostly work, there are likely *nix idioms that I'm not aware of that trip it up.

      If you (or anyone else) can sort out the problem and post the updated code I'd be very pleased. Especially if you describe the changes that were required.


      DWIM is Perl's answer to Gödel
      parv hit the size issue. I've tested with XP and 2000, and would like to suggest some massaging of the $rootPath... using 'c:' runs it incorrectly in the local dir, './' works correctly, anything else I tried ('C:', 'C:/', '/c'...) hung or was ignored.
      I'm not sure what would be required, but a translation of $rootPath to a windows friendly format if $^O =~/win/i might be nice.
Re: Directory tree explorer with stats reporting
by Scott7477 (Chaplain) on Mar 10, 2006 at 14:24 UTC
    I ran this code on my C: drive on a Windows XP system using ActiveState 5.8.7 build 813. I got a bunch of
    error lines as follows "Use of uninitialized value in adddition <+> at 535607.pl line 96". I haven't had
    time to dig through your code, but this functionality would be useful to me. Hope this helps..

    Scott

      Did you copy the code from the download link? (which sets it as text so you don't get the extra +'s that specify a wrapped line)?

        I doublechecked that and that wasn't the problem. Then I ran it and it worked fine...no errors at all.
        Who knows? User error, most likely:)
        A handy utility anyhow.

        Thanks,

        Scott

      Seems like you encountered a state where the path length may be more than the program (underlying -s function) could handle. I can easily reproduce it by creating a circular symbolic link on FreeBSD. After 32|33 levels, size is undef, resulting in Use of uninitialized value in adddition <+> ... message.

      I will post the updated program -- unless somebody beats me to it -- to not follow symbolic links.

      Update (later that day): Problem that i observed was due to circular symbolic links (unresolved till 3[23] levels); there was no problem w/ 60+ levels of directories.

        Ouch! Thanks for that work parv and I'd appreciate the update posted.


        DWIM is Perl's answer to Gödel