Coding Domain

Perl Programming: Working with Files


Working with directories in Perl
In this tutorial we use a recursive function to browse through all files, including sub directories from a certain path. To make our function usable in an univeral way, we also include a technique called a callback. Our callback function defined what should happen when a file is found, instead of implementing that code into the browsedir function

Reading the directory
It's very easy in Perl to browse through directories. You need to open a directory with opendir(), read each file in it using readdir(), and finally close the directory with closedir(). That first part is used here below. The subroutine doesn't return a array with all the files. It uses the subroutine reference you passed through, to report the file.

Alternatives
There are some alternatives to browse through a directory. You can, for example, use the <*> statement. This however, starts an other program. The 'function' returns an array with all the filenames found. That means you use a lot of system memory. I definitely don't recommend using this.

First part of the directory browse program
#!/usr/bin/perl -w

use strict;


#######################################################################
## Browser using a callback

sub BrowseDir (&$$)
{ my($CallBack, $Dir, $Recurse) = @_;

  if(ref $CallBack ne 'CODE')
  {
    # second check if the compiler can't use the prototype
    # of this subroutine
    my($Package, $FileName, $Line) = caller;
    die "BrowseDir Error: 1st parameter needs to be a"
        "subroutine reference at $FileName line $Line\n";
  }

  local *DIR;   # Localize for the recursion.
  opendir(DIR, $Dir) or die "Can't open '$Dir': $!";
  if($Dir eq '/') { $Dir = ''; } # We already assume the / in the directory...

  while( my $File = readdir DIR )
  {
    if($File ne '.' && $File ne '..')
    {
      &$CallBack($Dir, $File);  # Call the callback subroutine
      if( $Recurse && -d "$Dir/$File" )
      {
        BrowseDir($CallBack, "$Dir/$File", $Recurse);
      }
    }
  }
  closedir(DIR);
}

Getting the input
We read from the standard input handle. This is properly your keybaord when you're running this program in a terminal window.

my $Directory;
my $Recurse;

GetDirectory:
{
  print "Directory: ";
  chomp( $Directory = <STDIN> );
  if( '' eq $Directory) { redo; }
  if( ! -e  $Directory) { print "That directory does not exist!\n"; redo GetDirectory; }
  if( ! -d  $Directory) { print "That is not a directory!\n";       redo GetDirectory; }
}

GetRecurse:
{
  print "Go through subdirs: ";
  $Recurse = lc(getc);  # There are better ways then getc, see perldoc -f getc
  chomp $Recurse;

  if   ($Recurse eq 'y') { $Recurse = 1; }
  elsif($Recurse eq 'n') { $Recurse = 0; }
  elsif($Recurse ne 1
     && $Recurse ne 0)
  {
    print qq["$Recurse"];
    print "Bad input [y/n]\n";
    redo GetRecurse;
  }
}

print "\nRecurse is $Recurse\n\n";

Calling the BrowseDir subroutine
Now, we need to call the BrowseDir function. This is a bit strange, if you never worked with callbacks. The point here is, that we define a BLOCK, and pass a reference to that BLOCK to the BrowseDir function.

We can pass the BLOCK directly because the BrowseDir subroutine is prototyped as (&$$). Perl now knows the first parameter is a subroutine or BLOCK. Otherwise, you should place the keyword sub before the BLOCK, and a comma after it.

my $I = 0;
BrowseDir
{
  $I++;
  print "$_[0]/$_[1]\n";
} $Directory, $Recurse;

print "\n$I file(s) found\n\n";

Written by Diederik van der Boor at 17 November 2001