My take on the problem: read a file and if filenames are found (judged by .txt
) open and read those, and carry on recursively. The posted code has some basic errors, discussed below.
I assume that all lines of a file need be printed first and then we go into the next file(s) (if found). The code below allows the filehandles to be closed; a slight variation of it that keeps them in an array and opened follows below.
use warnings;
use strict;
use feature 'say';
my $file = shift @ARGV || 'file.txt';
open my $fh, '<', $file or die "Can't open $file: $!";
recurse_open($fh);
sub recurse_open {
my ($fh) = shift;
my @files;
while (<$fh>) {
print;
if (/\b(.+?\.txt)\b/) {
push @files, $1;
}
}
say '---';
foreach my $file (@files) {
open my $fh_next, '<', $file or do {
warn "Can't open $file: $!";
next;
};
recurse_open($fh_next);
}
}
This prints
main file
file1.txt is in it
end of main file
---
file one, with
a line with file2.txt
end of one
---
file two, which has
a line with file3.txt
end of two
---
Just the file3,
no more filenames.
---
where the content of the file.txt
and files 1..3, used for testing, is clear I hope (separated by ---
). This follows up on all filenames present in a file, if it happens that there is more than one.
If the phrase "without destroying the filehandles" in the title means that the filehandles should be kept open (and collected), then just add them to an array as they are opened
open my $fh, '<', $file or die "Can't open $file: $!";
my @filehandles = ($fh);
recurse_open($fh, \@filehandles);
sub recurse_open {
my ($fh, $handles) = @_;
...
foreach my $file (@files) {
open my $fh_next, '<', $file or do {
warn "Can't open $file: $!";
next;
};
push @$handles, $fh_next;
recurse_open($fh_next, $handles);
}
}
Normally a (lexical) filehandle is closed when it goes out of scope. However, since each is now copied into an array defined in a larger scope they are kept as there is a reference for each.
Comments on the code in the question.
The most serious error is an apparent misunderstanding of what a filehandle is and does. The expression <$fh>
reads from the file that was associated with the filehandle $fh
when it was opened, where <>
is the operator version of readline. See I/O Operators in perlop.
This returns a line in the file and that is what you should work on, with chomp
, m//
etc, and not on the $fh
itself. With while (<$fh>)
(nothing else in the condition) the line is assigned to the special variable $_
, which is default for many things in Perl. The code above makes use of that.
Next, you don't actually match on and capture the filename but only match on .txt
. (That match uses the filehandle instead of a variable containing the line, and open
uses that filehandle in place of a filename, which is the filehandle confusion mentioned above.)
Then, I don't see the need for that dance around $set
, incrementing and decrementing it. Since you nicely relegated all this to a subroutine just use the filehandle in a variable. So I did away with the array. Please restore it if it is needed for some other reasons.
Finally:
Always start the program with use warnings;
and use strict;
. This is not some pedantry but directly helps catching errors, and enforces some very good practices, too.
Always check your open
call (open ... or ...
)
Use lexical filehandles (my $fh
) instead of globs (FH
), they are much better. Use three-argument version of open
If this is the whole purpose you might as well pass the filename to the recursive sub and have it open and read the file.