-1

Recursive file openings without destroying the filehandles in perl

#!usr/bin/perl
   $set=1; 
   open (MYFILE, 'file1.txt'); 
   $array[$set]=\*MYFILE; 
   printit ($array[$set]); 

   sub printit {    
     ($array[$set])=shift;    
     $fh=<$array[$set]>;
     while (<$fh>) {
       chomp($fh); 
       #print data in the file
       if($fh=~/\.txt/){
           #print $fh;
           open (files,"$fh");
           $set=$set+1;
           printit(*files);
           $set=$set-1;
          }
    } 
}   
file1.txt -file2.txt,file3.txt #assume file2.txt comes before file3.txt 
file2.txt-file4.txt file3.txt 

I want to open a file1.txt and print data in file1 and if i find file2.txt in the file open the file print the data and goes recursively until the file does not contain and .txt files present in it and comes back (transverse a tress) in our case file1->file2->file4->file3->file1 end program. I don't know why my program is not work. Thanks in advance*

zdim
  • 53,586
  • 4
  • 45
  • 72
Mike
  • 9
  • 3
  • Use strict and warnings, it may catch several issues here. Don't use bareword (global) filehandles or global variables (they should be declared with [my](https://perldoc.pl/functions/my)), and error check your opens: do `open my $file, 'file1.txt' or die "Open file1.txt failed: $!";`. `` won't work, as the `<>` operator has weird parsing rules and will interpret that as a glob, you can do `readline($array[$set])` instead (it's the same operation). – Grinnz Oct 07 '18 at 03:49
  • Please format the explanation so that it is readable ... ? At least start and we'll help. I tried to edit but I can't fully makes sense of it. – zdim Oct 07 '18 at 04:46
  • What do you mean by "_without destroying the filehandles_"? Keep them all, opened? Please clarify. – zdim Oct 07 '18 at 05:32
  • I did clean up and format your question but I don't understand the listing of files. Please edit to clarify. (If you don't like my edit you can undo it by clicking on "edited (time)" link right below the question, and then click on "rollback" under the previous version) – zdim Oct 07 '18 at 06:22
  • Always use `use strict; use warnings qw( all );`!!! – ikegami Oct 07 '18 at 09:51

1 Answers1

2

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.

zdim
  • 53,586
  • 4
  • 45
  • 72