6.11.4 Creating and Removing Directories: File::Path Module

6.11.4  Creating and Removing Directories: File::Path Module

   

When dealing with file copying or moving, if the destination for the copy or move action has a path specification with a sequence of directories and one or more directories in the path do not exist, we have to create the destination directory. The built-in function mkdir can be used in this case. We have to extract the path using the function File::Basename::dirname and then split it using the directory separator, and then create the directories one by one from top to bottom. The following program shows how this can be done.

 Program 6.20

#!/usr/bin/perl
#file myMkpath.pl

use strict;
use Cwd;

my ($dirName, $dirSeparator);
my ($rootDir, @dirs);
$dirSeparator = "/";
$rootDir = "/";
$permissions = "0777";

$dirName = $ARGV[0];
die "Usage: myMkpath.pl directoryName\n" unless $dirName;
@dirs = split ($dirSeparator, $dirName);

my $oldDir = Cwd::cwd ();
$dirs[0] = $rootDir if $dirs[0] =~ /^$/;

my $i;
for ($i = 0; $i <= $#dirs; $i++){
  unless (-e $dirs[$i]){
    print "Creating directory " . $dirs[$i] . "\n";
    mkdir ($dirs[$i], $permissions) or die "Cannot create $dirs[$i]: $!";
  }
  chdir ($dirs[$i]) or die "Cannot chdir to $dirs[$i]: $!";
  print "Just chdired to $dirs[$i]\n";
}

chdir ($oldDir);

The program myMkpath.pl works only in a Unix of Unix-like environment. For it to work on a Windows machine or a Macintosh (pre-OS X), the values $dirSeparator and $rootDir have to be given appropriate values for the operating system. We look at such issues in Section 6.11.6.

The program uses the Cwd module to use the function cwd to obtain the current working directory. The program obtains the name of the directory to create from the command line. The directory specified can be an absolute directory or a relative specification of path.

The program splits the name of the directory given around the directory separator which is / in Unix. If the directory given is relative, e.g., a/b/c/d/e/f/g/h, the path does not start with a /. However, if the directory given is absolute, e.g., /home/kalita/perl/file/a/b/c/d/e/f, the initial character of the path is /, which is the root directory in addition to being the directory separator. When such an absolute file address is split about the directory separator /, we get the empty string as the first element for the separated list of directories, @dirs in this case. Thus, in the case of an absolute directory address, we set the initial directory to $rootDir.

Following the initial pre-processing of the list of directories on the path, the program goes into a for loop where it traverses each element in the directory list from the beginning. For example, if the path is relative and is a/b/c/d/e/f/g/h, the loop first looks at the directory a. If this directory does not exist, it creates this directory if it can. The program then chdirs to a, whether it is newly created or
existed from before. Next, from within this directory a, the program creates the directory b, and so on and so forth. If the path is absolute, e.g., /home/kalita/perl/file/a/b/c/d/e/f, the program first chdirs to / which always exists on a Unix machine. It next chdirs to /home which exists on some Unix or Unix-like machines such as Linux. If this directory does not exist, the program tries to create this directory if it is possible. Most users
do not have the permission to create a directory at the top level. If /home exists, the program next chdirs to /home/kalita. Once again, it is a directory most users cannot create. Then, it creates /home/kalita/perl if it does not exist and has the permission to create. A few example interactions with this program are given below.

 

% myMkpath.pl /home/kalita/perl/file/a/b/c/d/e/f

Just chdired to /

Just chdired to home

Just chdired to kalita

Just chdired to perl

Just chdired to file

Just chdired to a

Just chdired to b

Just chdired to c

Just chdired to d

Just chdired to e

Just chdired to f

% myMkpath.pl /a/b/c/d/e/f

Just chdired to /

Creating directory a

Cannot create a: Permission denied at myMkpath.pl line 27.

% myMkpath.pl a/b/c/d/e/f/g/h

Just chdired to a

Just chdired to b

Just chdired to c

Just chdired to d

Just chdired to e

Just chdired to f

Creating directory g

Just chdired to g

Creating directory h

Just chdired to h

 

The directories in the path have to be created one by one because mkdir can create a directory only one level at a time. This step-wise creation of directories can be avoided if we use File::Path module that provides us with a function called mkpath that is like mkdir, but that can create intermediate directories if necessary, and if it is able to. Using the File::Path module, the previous program can be written much more simply as the one given below.

 Program 6.21

!/usr/bin/perl
#file myMkpath1.pl

use strict;
use File::Path;

my ($dirName, $permissions);
$permissions = "0777";

$dirName = $ARGV[0];
die "Usage: myMkpath1.pl directoryName\n" unless $dirName;
if (-e $dirName){
   print "Directory $dirName exists\n";
   }
else{
   File::Path::mkpath ($dirName, $permissions) 
        or die "Cannot create $dirName: $!";
   }

This program simply calls mkpath to create the directory hierarchy if needed. Here is a simple interaction with this program.

 

%myMkpath1.pl a/b/c/d/e/f/g/h

mkdir a

mkdir a/b

mkdir a/b/c

mkdir a/b/c/d

mkdir a/b/c/d/e

mkdir a/b/c/d/e/f

mkdir a/b/c/d/e/f/g

mkdir a/b/c/d/e/f/g/h

 

Here, the program had to create every directory in the path.

Now, we can write a more robust version of the filecopy.pl program discussed in Section 6.11.1. The program is given below.

 Program 6.22

#!/usr/bin/perl
#file filecopy1.pl
use File::Copy;
use File::Basename;
use File::Path;
use strict;

my ($src, $dest) = @ARGV;
my $permissions = "0777";

if (!$src or !$dest){
     print "Usage: filecopy sourcefile destfile\n";
     exit 1;
   }

my $destDirname = File::Basename::dirname ($dest);
unless (-e $destDirname){
  File::Path::mkpath ($destDirname, $permissions) or
       die "Cannot mkpath $destDirname: $!";
}

File::Copy::copy ($src, $dest) or die "Cannot copy $src to $dest: $!";

This program parses the destination file name to find the path. If the destination directory does not exist, it creates it using File::Path::mkpath. Then, the program copies the source file to the destination file. Below, we see a couple of calls to to the program.

 

% filecopy1.pl mkdir.pl mkdir1.pl

% filecopy1.pl mkdir.pl a/b/c/d/mkdir1.pl

mkdir a

mkdir a/b

mkdir a/b/c

mkdir a/b/c/d

 

The module File::Path also provides a function called rmtree to remove a directory structure with all contained files and directories. The removal is recursive and is final. So, one should be cautious in using this function. The following program takes a directory name as a command line argument to remove recursively, asks for confirmation, and if confirmed calls rmtree on the directory.

 Program 6.23

#!/usr/bin/perl
#file rmtree.pl
use File::Path;
use strict;

my $dir = $ARGV[0];
if (!$dir){
     print "Usage: rmtree.pl directoryName";
     exit 1;
   }

print "Are you sure you want to delete $dir recursively? (Y|N): ";
my $response = ;
chomp $response;
if ($response =~ /^y$/i){ 
     File::Path::rmtree ($dir) or warn "Cannot rmtree $dir: $!";
   }