[comp.lang.perl] Recursive Subroutines - Help sought

lengge@chx400.switch.ch (Thomas Lenggenhager) (10/29/90)

I am just a beginner with perl. I use it on a SUN 4/490, the version is
	$Header: perly.c,v 3.0.1.8 90/10/16 10:14:20 lwall Locked $
	Patch level: 36

This is my first perl program which should list all file entries of a
directory recursively. However what it does is listing the files of the
directory I give as input and the files of the first subdirectory but
then the program stops!
  It seems to me, that some variables are not recovered correctly on returning
from the subroutine, even by declaring them as local.

I know that I could have that much more easily, but the main intent was to
do some processing on each file of a sub-tree.

Who can find the bug in my program, or who suggests a better solution to my
problem.

Many thanks for your help in advance,
Thomas Lenggenhager
SWITCH

===============================================================================
     Thomas Lenggenhager, SWITCH, ETH-Zentrum, CH-8092 Zurich, Switzerland
          INET: lenggenhager@verw.switch.ch  | Tel: +41-1-261'8178
          UUCP: ..!mcsun!chx400!lenggenhager | Fax: +41-1-261'8133
         X.400: S=lenggenhager;OU=verw;O=switch;P=switch;A=arcom;C=ch

Here follows my perl program:
===============================================================================
#
# Ask for directory name and list fileentries recursively
#
print "Directory: ";
$dirname = <STDIN>;
chop($dirname);
do read_dir($dirname);
exit;

sub read_dir {
#
# Print all full filenames recursively to STDOUT
#
	local($dirname) = @_;
	local($file, $filename);
	open($file, "/bin/ls -AF $dirname|") || return ;
	while (<$file>) {
		$filename = $_;
		chop($filename);
		if ($filename =~ s|/$||) {
			$dirname = $dirname . "/" . $filename;
			print $dirname, "/\n";
			do read_dir($dirname);
		}
		else {
			print $dirname . "/" . $filename, "\n";
		}
	}
	close($file);
}
-- 
===============================================================================
     Thomas Lenggenhager, SWITCH, ETH-Zentrum, CH-8092 Zurich, Switzerland
          INET: lenggenhager@verw.switch.ch  | Tel: +41-1-261'8178
          UUCP: ..!mcsun!chx400!lenggenhager | Fax: +41-1-261'8133

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (10/30/90)

In article <1990Oct29.140931.5498@chx400.switch.ch> lengge@chx400.switch.ch (Thomas Lenggenhager) writes:
: This is my first perl program which should list all file entries of a
: directory recursively. However what it does is listing the files of the
: directory I give as input and the files of the first subdirectory but
: then the program stops!
:   It seems to me, that some variables are not recovered correctly on returning
: from the subroutine, even by declaring them as local.
: ...
: Here follows my perl program:
: ===============================================================================
: #
: # Ask for directory name and list fileentries recursively
: #
: print "Directory: ";
: $dirname = <STDIN>;
: chop($dirname);
: do read_dir($dirname);
: exit;
: 
: sub read_dir {
: #
: # Print all full filenames recursively to STDOUT
: #
: 	local($dirname) = @_;
: 	local($file, $filename);
: 	open($file, "/bin/ls -AF $dirname|") || return ;
: 	while (<$file>) {

Okay, your problem is that you're thinking of $file as a filehandle.  It
isn't.  It's a variable containing a filehandle--the null filehandle
in this case.  Declaring it local doesn't change this, so all your opens
refer to the same filehandle.  And so each open closes the previous one.

What you want to do is

	local($file) = $seq++;
	open($file,...)

where global $seq has been initialized to something like "FH001".

Or, even better, don't use ls, but readdir().  Here's an example:

#!/usr/bin/perl

&dodir('.');
sub dodir {
    local($dir,$nlink) = @_;
    local($dev,$ino,$mode);
    ($dev,$ino,$mode,$nlink) = stat('.') unless $nlink;	# first time

    opendir(DIR,'.') || die "Can't open $dir";
    local(@filenames) = readdir(DIR);
    closedir(DIR);

    if ($nlink == 2) {			# this dir has no subdirectories
	for (@filenames) {
	    next if $_ eq '.';
	    next if $_ eq '..';
	    print "$dir/$_\n";
	}
    }
    else {				# this dir has subdirectories
	for (@filenames) {
	    next if $_ eq '.';
	    next if $_ eq '..';
	    $name = "$dir/$_";
	    print $name,"\n";

	    ($dev,$ino,$mode,$nlink) = lstat($_);
	    next unless -d _;

	    chdir $_ || die "Can't cd to $name";
	    &dodir($name,$nlink);
	    chdir '..';
	}
    }
}

Larry

evans@decvax.dec.com (Marc Evans) (10/31/90)

In article <1990Oct29.140931.5498@chx400.switch.ch>, lengge@chx400.switch.ch (Thomas Lenggenhager) writes:
|> Who can find the bug in my program, or who suggests a better solution to my
|> problem.

How about this instead:

print "Directory: ";
$dirname = <STDIN>;
chop($dirname);
do read_dir($dirname);
exit 0;

sub read_dir
{   local($dirname)=@_;
    opendir(DIR,$dirname);
    local(@entries)=readdir(DIR);
    closedir(DIR);
    local($entry);
    while ( $entry=shift(@entries) )
    {   next if ($entry eq "." || $entry eq "..");
        if (-d $entry)
        {   do read_dir("$dirname/$entry");
            print "$dirname/\n";
        }
        else
        {   print "$dirname/$filename\n";
        }
    }
}

-- 
===========================================================================
Marc Evans - WB1GRH - evans@decvax.DEC.COM  | Synergytics     (603)635-8876
      Unix and X Software Contractor        | 21 Hinds Ln, Pelham, NH 03076
===========================================================================