views:

197

answers:

7

Hi,

I need to delete all content (files and folders) under a given folder. The problems is the folder has millions of files and folders inside it. So I don't want to load all the file names in one go.

Logic should be like this:

  • iterate a folder without load everything
  • get a file or folder
  • delete it (verbose that the file or folder "X" was deleted)
  • go to the next one

I'm trying something like this:

sub main(){
  my ($rc, $help, $debug, $root)   = ();
  $rc = GetOptions ( "HELP"           => \$help,
                     "DEBUG"          => \$debug,
                     "ROOT=s"         => \$root);

  die "Bad command line options\n$usage\n" unless ($rc);
  if ($help) { print $usage; exit (0); }

  if ($debug) {
      warn "\nProceeding to execution with following parameters: \n";
      warn "===============================================================\n";
      warn "ROOT = $root\n";

  } # write debug information to STDERR

  print "\n Starting to delete...\n";  

  die "usage: $0 dir ..\n" unless $root;
  *name = *File::Find::name;
  find \&verbose, @ARGV;

}

sub verbose {
    if (!-l && -d _) {
        print "rmdir $name\n";
    } else {
        print "unlink $name\n";
    }
}

main();

It's working fine, but whenever "find" reads the huge folder, the application gets stuck and I can see the system memory for Perl increasing until timeout. Why? Is it trying to load all the files in one go?

Thanks for your help.

+6  A: 

What's wrong with:

`rm -rf $folder`; // ??
Jeff B
I'd like to verbose the process, can I?
André Diniz
`rm` has a `-v` option, which would do what you want under unix-like OS's, but as you stated you are under Windows this does not help you.
Adam Bellaire
@Adam - there are DOS ports (numerous) of Unix commands. I bet one of them, can do this :)
DVK
I use GnuWin32 utilities nearly every day. Here's a link to the package that contains `rm`: http://gnuwin32.sourceforge.net/packages/coreutils.htm
daotoad
@DVK, @daotoad: Of course there are, and those are great examples. What I was getting at was that `rm -rf` does have a `-v` option, but using `rm` in backticks is not a portable solution, which seemed to escape the op in his initial comment on this answer.
Adam Bellaire
+4  A: 

You can use File::Find to systematically traverse the directory and delete the files and directories under it.

codaddict
I am tempted to ask, *What would be the point?*
Sinan Ünür
@Sinan: OP does not want to delete the parent directory.
codaddict
I'd like to verbose all the process.
André Diniz
@Sinan: And for whatever reason, the OP wants all the files printed as they're deleted. @André: Look at `File::Find`. It calls an arbitrary subroutine for every file. If you want to print the filename, print the filename.
Jefromi
@André: Of course, as Sinan pointed out, you can probably call your system's recursive verbose remove on all the contents of the directory. You don't really have to reimplement it.
Jefromi
I will try and let you know.
André Diniz
+2  A: 

OK, I gave in and used Perl builtins but you should use File::Path::rmtree which I had totally forgotten about:

#!/usr/bin/perl

use strict; use warnings;
use Cwd;
use File::Find;

my ($clean) = @ARGV;
die "specify directory to clean\n" unless defined $clean;

my $current_dir = getcwd;
chdir $clean
    or die "Cannot chdir to '$clean': $!\n";

finddepth(\&wanted => '.');

chdir $current_dir
    or die "Cannot chdir back to '$current_dir':$!\n";

sub wanted {
    return if /^[.][.]?\z/;
    warn "$File::Find::name\n";
    if ( -f ) {
        unlink or die "Cannot delete '$File::Find::name': $!\n";
    }
    elsif ( -d _ ) {
        rmdir or die "Cannot remove directory '$File::Find::name': $!\n";
    }
    return;
}
Sinan Ünür
Thanks for reply, but whenever "find" function read the hude folder, the application get stuck and I can see the system memory for Perl increasing until timeout. Why? Is it trying to load all the files in one go? Any idea?
André Diniz
+7  A: 

The remove_tree function from File::Path can portably and verbosely remove a directory hierarchy, keeping the top directory, if desired.

use strict;
use warnings;
use File::Path qw(remove_tree);

my $dir = '/tmp/dir';
remove_tree($dir, {verbose => 1, keep_root => 1});

Pre-5.10, use the rmtree function from File::Path. If you still want the top directory, you could just mkdir it again.

use File::Path;

my $dir = '/tmp/dir';
rmtree($dir, 1);  # 1 means verbose
mkdir $dir;
toolic
Thanks for reply, but whenever "rmtree" function reads the hude folder, the application get stuck and I can see the system memory for my Perl application just increasing. Why? Is it trying to load all the files in one go? Any idea how to avoid this?
André Diniz
Yes, apparently it is. It loads all the things in the directory to recursively delete them. Looks like there's no reason it couldn't be made iterative. See http://github.com/gitpan/File-Path/blob/master/Path.pm#L333
Schwern
+5  A: 

The perlfaq points out that File::Find does the hard work of traversing a directory, but the work isn't that hard (assuming your directory tree is free of named pipes, block devices, etc.):

sub traverse_directory {
    my $dir = shift;
    opendir my $dh, $dir;
    while (my $file = readdir($dh)) {
        next if $file eq "." || $file eq "..";
        if (-d "$dir/$file") {
            &traverse_directory("$dir/$file");
        } elsif (-f "$dir/$file") {
            # $dir/$file is a regular file
            # Do something with it, for example:
            print "Removing $dir/$file\n";
            unlink "$dir/$file" or warn "unlink $dir/$file failed: $!\n";
        } else {
            warn "$dir/$file is not a directory or regular file. Ignoring ...\n";
        }
    }
    closedir $dh;
    # $dir might be empty at this point. If you want to delete it:
    if (rmdir $dir) {
        print "Removed $dir/\n";
    } else {
        warn "rmdir $dir failed: $!\n";
    }
}

Substitute your own code for doing something with a file or (possibly) empty directory, and call this function once on the root of the tree that you want to process. Lookup the meanings of opendir/closedir, readdir, -d, and -f if you haven't encountered them before.

mobrule
Thanks, I will try and let you know.
André Diniz
U-huuu! It's working! Thanks a lot @mobrule!
André Diniz
+1  A: 

Download the unix tools for windows and then you can do rm -rv or whatever.

Perl is a great tool for a lot of purposes, but this one seems better done by a specialised tool.

justintime
Hasn't been updated in a decade. Use this instead: http://gnuwin32.sf.net/
daxim
A: 

Here's a cheap "cross-platform" method:

use Carp    qw<carp croak>;
use English qw<$OS_NAME>;
use File::Spec;  

my %deltree_op = ( nix => 'rm -rf %s', win => 'rmdir /S %s' );

my %group_for
    = ( ( map { $_ => 'nix' } qw<linux UNIX SunOS> )
      , ( map { $_ => 'win' } qw<MSWin32 WinNT>    )
      );

my $group_name = $group_for{$OS_NAME};
sub chop_tree { 
   my $full_path = shift;
   carp( "No directory $full_path exists! We're done." ) unless -e $full_path;
   croak( "No implementation for $OS_NAME!" ) unless $group_name;
   my $format = $deltree_op{$group_name};
   croak( "Could not find command format for group $group_name" ) unless $format;
   my $command = sprintf( $format, File::Spec->canonpath( $full_path ));
   qx{$command};
}
Axeman