6.5 Finding the Current Working Directory
In the previous section, we assumed the program is situated in the home directory of the user. This need not always be the case. The Perl language does not have a built-in function to find the current working directory. But, it provides a standard module called Cwd that has such a function. This package comes with the Perl language source distribution. The function is called cwd. It is called with no arguments to find the current working directory inside a program. Modules are discussed in Chapter 5.
Below we have a variation of the program given in the previous section.
Program 6.6
#!/usr/bin/perl use Cwd; use strict; $"= "\n"; my $initDir = cwd (); print "The current working directory is: ", $initDir, "\n"; my $path = "/home/kalita/classes/"; chdir ($path) or die "Cannot chdir to $path: $!"; print "The current working directory is: ", cwd (), "\n"; opendir (DIR, ".") or die "Cannot open $path: $!"; my @files = readdir DIR; print "@files\n"; chdir ($initDir); print "The current working directory is: ", cwd (), "\n";
The program first declares that it uses the Cwd package using the use statement. It finds and prints the current working directory by making a call cwd(). Next, it changes the current working directory to the one specified in the $path variable. It opens the current working directory, reads the list of files and prints the list. Having done that, it calls chdir with the name of the original directory to change
the current working directory back to what it was before. The program prints the current working directory in several places to make sure that things happen as expected. The output of the program may look like what is given below.
The current working directory is: /home/kalita/classes/cs301/perlcode/files The current working directory is: /home/kalita/classes . .. cs420-520 cs482-582 cs586 cs145 cs589 cs460-560 penn-classes phd-exams cs305 cs112 cs583 cs480-580 cs301 lfix.tex tutoring-projects decl.tex penn-comp-ling-class cs472-572 acm-prog-contest cs401 The current working directory is: /home/kalita/classes/cs301/perlcode/files
Finally, we have a program that reads and prints the names of files recursively. It is a variation of the recursive programs we have seen earlier in the chapter. But, unlike the previous programs that use qualified relative names to access a directory as well as print names of files and directories, this program chdirs to a directory before reading its contents. It does so recursively. However, it also remembers which directory to go back to once it has finished reading a certain directory. The program is given below.
Program 6.7
#!/usr/bin/perl
use strict;
use Cwd;
sub printdirR{
my ($recursionLevel, @FDList) = @_;
my $first = shift @FDList;
if (!$first){return ()}
elsif (-f $first){
print " " x $recursionLevel, "$first\n";
map {printdirR ($recursionLevel, $_);} @FDList;
}
elsif (-d $first){
my $currdir = cwd ();
chdir ($first) or warn "Cannot chdir to $first: $!";
opendir DIR, "." or warn "Cannot opendir $first: $!";
my @files = sort (readdir DIR);
closedir DIR;
@files = grep {$_ !~ /^[.]{1,2}$/} @files;
print " " x $recursionLevel, "$first/\n";
printdirR ($recursionLevel + 1, @files);
chdir ($currdir);
map {printdirR ($recursionLevel, $_)} @FDList;
}
}
#main program
$" = ", ";
my @FDToPrint = @ARGV;
if (!@FDToPrint) {@FDToPrint = ".";}
map {printdirR (0, $_);} (sort @FDToPrint);
If the main program is not called with any command-line arguments, the program decides to print the files and directories contained within the current working directory. It calls the recursive subroutine printdirR for each element of the list @FDToPrint by mapping the function call on the list. The statement that maps the call is given below.
map {printdirR (0, $_);} (sort @FDToPrint);
printdirR is called with two arguments: a number that denotes the level of recursion and an element from the list obtained by sorting @FDToPrint. The first argument to printdirR is used to print the names of files and directories in a nicely indented manner.
The subroutine printdirR first separates out the two arguments passed to it. The list of files and directories is called @FDList. It obtains the first element of @FDList and calls it $first. Next, we have an if-elsif-elsif statement that recursively prints contained files and directories. If $first is null, i.e., if @FDList is empty, the subroutine does not do anything, but returns immediately.
If $first is a regular file, the subroutine prints the name of the file. It precedes the name of the file by a number of spaces based on the number of recursion levels the program is in at the time. Once the name of the file is printed with the correct level of indentation, it calls printdirR recursively on the remaining list of files and directories: @FDList. It passes the current level of recursion to the recursive calls.
If $first is a directory, we need to traverse this directory recursively and print the files and directories contained within it. For this purpose, the subroutine has to change the current working directory recursively if it goes down levels of directory. Once a recursive call is over, the subroutine has to remember which directory to go back to. Therefore, before making any recursive calls, it remembers where it is by executing the following statement.
my $currdir = cwd ();
The subroutine than goes down to the directory $first by doing a chdir. It reads the contents of the current working directory, and removes the two special files . and .. corresponding to the current directory and the parent directory, recursively. Before doing anything else, the subroutine prints the name of the directory $first, and appends a / to
indicate that it is a directory and not a simple file. Next, it executes the following statements.
printdirR ($recursionLevel + 1, @files);
chdir ($currdir);
map {printdirR ($recursionLevel, $_)} @FDList;
The first statement calls printdirR recursively on each of the files and directories contained within the original directory $first. Since, this makes the program move down one level deeper in the file hierarchy, the program increases the recursion level by 1 as it makes these recursive calls. This ensures that the contained files and directories are printed with the proper number of spaces in the front. These recursive calls may cause the program to change the current working directory many times. Once all the recursive calls are over, the subroutine comes back to the working
directory $currdir which corresponds to the directory named in $first. This ensures that after all subtrees of the file hierarchy are read, the program is back where it started. If we do not make this call to the remembered directory, the subroutine would not know where it is supposed to be in the file hierarchy. After the subroutine reestablishes the current working directory, it again makes recursive calls to printdirR on the remaining list of files and directories stored in the list @FDList. This time, the recursion level is not
increased because it is reading files and directories at the same level as $first.
Assuming the program is stored in the file printddirR.pl and there is a directory named a in the directory where the program is contained, we can making the following call.
%printdirR.pl a
The output of the call is something like what is given below.
a/
a1
a2
a3
ad1/
ad1a
ad1b
ad1c
ad1d1/
ad2/
ad3
As we can see, the files and directories are printed nicely indented to show the inclusion structure or the file hierarchy. The program indicates directories by printing a / at the end.
Finally, we end this section with a comment on the recursive subroutine. In the if-elsif-elsif statement that we see in the program, it is not really necessary to have the following if statement.
if (!$first){return ()}
It is because the program does nothing for the case when $first is empty. We write this to keep the various recursive subroutines in this chapter maintain consistent structure. However, it is acceptable in this case to have a program that has just an if-elsif structure with the if testing whether
the first element of the list is a file and elsif testing whether the first element is a directory.