6.8 Deleting Directories Recursively
6.8 Deleting Directories Recursively
We can delete a directory using the rmdir function. It removes a directory if the program’s owner has permission to write to the directory. unlink, the command used to remove a file should not be used to remove a directory.
Now, we discuss a program that is an extension of the immediately preceding program. Just like the previous program, it recursively removes files that have certain prespecified extensions. In addition, if a directory is already empty to begin with, or becomes empty after the files with the given extensions are removed, the directory is also removed. We modify deleteR subroutine in the previous program to do this. The modified subroutine is given below.
sub deleteR{
my @FDList = @_;
my $first = shift @FDList;
if (!$first){
return ();
}
elsif (-f $first and hasExtension($first, @extensions)){
unlink $first;
}
elsif (-d $first){
opendir DIR, $first or warn "Cannot open directory $first: $!";
my @files = readdir DIR;
closedir DIR;
@files = grep {$_ !~ /^[.]{1,2}$/} @files;
@files = map {"$first/$_"} @files;
my @simpleFiles = grep -f, @files;
my @directories = grep -d, @files;
my @filesToRemove = grep {hasExtension($_, @extensions)} @simpleFiles;
my @filesToKeep = grep {!hasExtension($_, @extensions)} @simpleFiles;
my $noOfFilesRemoved = unlink @filesToRemove;
my $noOfDirectoriesRemoved = 0;
my $directory;
foreach $directory (@directories){
if (deleteR ($directory)){
$noOfDirectoriesRemoved++;
}
}
if (!@filesToKeep and
($noOfFilesRemoved == $#filesToRemove + 1) and
($noOfDirectoriesRemoved == $#directories + 1) ){
rmdir ($first) or warn "Cannot remove directory $first: $!";
return 1;
}
else{
return 0;
}
}
}
As before, the subroutine returns nothing if the argument given to it is the empty list. Also like the previous version, deleteR removes the first element of the argument list if it is a file and has a prespecified extension in its name. The difference comes when the first element of the argument given to deleteR is a directory. As usual, it reads the list of contained files and directories, removes the . and .. entries, and then separates out the contents of the
current directory into two lists: @simpleFiles and @directories. It then obtains the list of simple files to remove and stores them in @filesToRemove and the list of simple files to keep in @filesToKeep. Then, it executes the statement given below.
my $noOfFilesRemoved = unlink @filesToRemove;
unlink can take a list as argument. It removes as many files as possible from this list. It then returns the number of files it is able to remove. We note that if unlink is used in this manner, it is not possible to find which files are removed and which files remain afterward to issue warnings or perform other actions.
Next, the program performs the following statements.
foreach $directory (@directories){
if (deleteR ($directory)){
$noOfDirectoriesRemoved++;
}
}
This is where we loop over the list of directories contained and make a recursive call to deleteR for each directory. This recursive call is made as the conditional of an if statement. deleteR returns 1 if it succeeds in deleting a directory. If the subroutine can delete a directory, it increments the number of directories removed. Keeping count of the number of directories removed is crucial if we want to remove directories recursively.
Then, the following group of statements is executed to determine what deleteR should return.
if (!@filesToKeep and
($noOfFilesRemoved == $#filesToRemove + 1) and
($noOfDirectoriesRemoved == $#directories + 1) ){
if (rmdir ($first)){
return 1;
}
else {
warn "Cannot remove directory $first: $!";
return 0;}
}
else{
return 0;
}
We return 0 if we are unable to delete the directory $first. We can delete a directory only if three conditions are satisfied. First, it must be true that we determine that there are no simple files in it to keep. In other words, there are no simple files to begin with. The second condition to satisfy is that we are able to remove all simple files that we should remove. In other words, no unforeseen conditions occurred (such as lack of permission) allowing us to remove all the files that we want to remove. The third condition is that the subroutine is able to remove all the directories included during the recursive calls it makes. If
all these conditions are satisfied, the subroutine deletes the directory $first and returns a value of 1. If the conditions are not satisfied, the subroutine returns a zero. This returned value is used to determine if all the directories are removed recursively.
