4

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.

Telemachus
  • 19,459
  • 7
  • 57
  • 79
André Diniz
  • 306
  • 3
  • 15
  • The **issue** is an issue only if you make it an issue. Why do you want to write `rm` or `rd /q/s` (depending on your OS)? – Sinan Ünür Apr 02 '10 at 17:03
  • I don't agree that it's necessarily better to use rm or rd. Using the perl builtins is more portable. – frankc Apr 02 '10 at 17:11
  • I need to delete file by file and verbose this, is it possible? I'm under Windows S.O. and rmdir just got stuck. I'd like to verbose the process. – André Diniz Apr 02 '10 at 17:26
  • 3
    rmdir is probably not stuck, just working on deleting millions of files. "Verbosing" the process will make it take even longer, and will millions of lines of output really help you? – Jeff B Apr 02 '10 at 17:36
  • Yes, I know that. Okay, forget about the delete. For some reason, I want to list all files on the screen. – André Diniz Apr 02 '10 at 17:49
  • 1
    See my post, you need `finddepth` not `find`. Specifying `no_chdir => 1` in option is better than doing `*name = *File::Find::name;`. Also, you forgot to skip `.` and `..` in `sub verbose`. – Sinan Ünür Apr 02 '10 at 17:57

7 Answers7

7

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
  • 57,801
  • 17
  • 75
  • 117
  • 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 Apr 02 '10 at 19:03
  • 1
    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 Apr 03 '10 at 20:57
6

What's wrong with:

`rm -rf $folder`; // ??
Jeff B
  • 29,943
  • 7
  • 61
  • 90
  • I'd like to verbose the process, can I? – André Diniz Apr 02 '10 at 17:27
  • `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 Apr 02 '10 at 17:37
  • 1
    @Adam - there are DOS ports (numerous) of Unix commands. I bet one of them, can do this :) – DVK Apr 03 '10 at 00:29
  • 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 Apr 03 '10 at 00:34
  • @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 Apr 03 '10 at 00:38
6

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.

mob
  • 117,087
  • 18
  • 149
  • 283
  • I got an error in the 3rd line `opendir my $dh, $dir;`. Solved it by replacing with the following: `my $dh; opendir $dh, $dir;` The rest of the code is working fine. Thanks – Pit Jan 11 '11 at 15:03
4

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

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
codaddict
  • 445,704
  • 82
  • 492
  • 529
  • @Sinan: OP does not want to delete the parent directory. – codaddict Apr 02 '10 at 17:07
  • I'd like to verbose all the process. – André Diniz Apr 02 '10 at 17:30
  • @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. – Cascabel Apr 02 '10 at 17:33
  • @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. – Cascabel Apr 02 '10 at 17:35
2

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
  • 116,958
  • 15
  • 196
  • 339
  • 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 Apr 02 '10 at 18:42
1

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
  • 3,601
  • 4
  • 22
  • 37
0

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
  • 29,660
  • 2
  • 47
  • 102