Home > Software design >  How can I modify this Perl subroutine so that it prints the contents of the file, using print_file?
How can I modify this Perl subroutine so that it prints the contents of the file, using print_file?

Time:11-23

Reading Learning Perl the Hard Way by Allen B. Downey.

Exercise 1.1 says:

The glob operator takes a pattern as an argument and returns a list of all the files that match the given pattern. A common use of glob is to list the files in a directory.

my @files = glob "$dir/*";

The pattern $dir/* means “all the files in the directory whose name is stored in $dir”. See the documentation of glob for examples of other patterns. Write a subroutine called print dir that takes the name of a directory as a parameter and that prints the file in that directory, one per line.

I did that:

#!/usr/bin/perl

sub print_dir {

        my $dir = shift;

        my @files = glob "$dir/*";

        foreach my $file (@files) {

                print "$file\n";
        }

}

print_dir @ARGV;

Then Exercise 1.2 says 'Modify the previous subroutine so that instead of printing the name of the file, it prints the contents of the file, using print_file.'

I'm struggling with this one. I have a script that prints the contents of a file:

#!/usr/bin/perl

use strict;
use warnings;

sub print_file {
        my $file = shift;
        open(my $FILE, $file) 
             or die $!;
        while (my $line = <$FILE>) {
                print $line;
        }
}

sub cat {
        foreach my $file (@_) {
                print_file $file;
        }
}

cat @ARGV;

And then I have the other script above that prints the names of all files in a directory. So this is what I have so far trying to get all files in a directory and then print the contents of all of those files:

#!/usr/bin/perl

use strict;
use warnings;

sub print_file {
        my $file = shift;
        open(my $FILE, $file)
             or die $!;
        while (my $line = <$FILE>) {
                print $line;
        }
}

sub print_dir {
        my $dir = shift;
        my @files = glob "$dir/*";

        while (my $dir = shift)   {
                foreach my $file (@files) {
                        print_file "$file";
                }
        }
}

print_dir @ARGV;

And obviously it's not working and there is no error message either.

CodePudding user response:

You add a line feed to the file name for no reason.

print_file "$file\n";

should be

print_file $file;

By the way, it's a bad practice to use global vars (like FILE) for nothing. And it's a good idea to check open for errors as it's quite prone to them.

open my $FILE, $file
   or die "Can't open \"$file\": $!\n";

while (my $line = <$FILE>) {
   ...
}

With your bug, this would have output something like

Can't open "./some_file
": No such file or directory

CodePudding user response:

Following code snippets demonstrate two possible solutions to your exercises.

Perl code can be very concise in expression of desired algorithm (principle of: brevity is the soul of wit).

Note 1: code verifies that passed argument is a directory

Note 2: code skips the output of content of directory as it is not a file

use strict;
use warnings;
use feature 'say';

print_dir($_) for @ARGV;

sub print_dir {
    my $dir = shift;
    
    die "$dir isn't a directory" unless -d $dir;
    
    say for glob("$dir/*");
}

Modified code to print content of the files

use strict;
use warnings;
use feature 'say';

print_dir($_) for @ARGV;

sub print_dir {
    my $dir = shift;

    die "$dir isn't a directory" unless -d $dir;
    
    for my $fname ( glob("$dir/*") ) {
        next if -d $fname;             # skip directories
        say "\n" . '-' x 25 
          . "\n" . $fname 
          . "\n" . '-' x 25;
        open my $fh, '<', $fname
            or die "Couldn't open $fname";
        print while <$fh>;
        close $fh;
    }
}

Reference:

Recomendation:

  • Related