Warm tip: This article is reproduced from stackoverflow.com, please click
perl filehandle

Multiple filehandles opening the same file

发布于 2020-03-29 20:59:24

I have grades.tsv file with three columns that show students' names, subjects and grades:

Liam    Mathematics 5
Liam    History 6
Liam    Geography   8
Liam    English 8
Aria    Mathematics 8
Aria    History 7
Aria    Geography   6
Isabella    Mathematics 9
Isabella    History 4
Isabella    Geography   7
Isabella    English 5
Isabella    Music   8

I wanted to calculate the average grade for each student and add it to a separate column. For this I used two filehandles DATA and OUT opening the same file:

use strict;
use warnings;

# Open file with grades for calculation of average grade for each student
open (DATA,"grades.tsv") or die "Cannot open file\n";

my %grade_sums;
my %num_of_subjects;

# Calculate sum of grades and number of subjects for each student
while( <DATA> ) {

   chomp;
   my ($name, $subject, $grade) = split /\t/;

   $grade_sums{$name} += $grade;
   $num_of_subjects{$name} += 1;
}

close DATA;


# Open file with grades again but this time for a purpose of adding a separate column with average grade and printing a result
open (OUT,"grades.tsv") or die "Cannot open file\n";

while ( <OUT> ) {
   chomp;
   my ($name, $subject, $grade) = split /\t/;

   # Calculate average grade
   my $average_grade = $grade_sums{$name} / $num_of_subjects{$name};
   my $outline = join("\t", $name, $subject, $grade, $average_grade);

   # Print a file content with new column
   print "$outline\n";
}

close OUT;

The code works but I am not sure if it is a proper way for this task. Is it a good practice or are there better ways that should be preferred?

Questioner
zubenel
Viewed
84
ikegami 2020-01-31 01:45

Reopening the file is fine. One alternative would be to seek to the start of the file.

use Fcntl qw( SEEK_SET );

seek(DATA, 0, SEEK_SET);

Seeking is more efficient because it doesn't have to check for permissions, etc. It also guarantees that you get the same file (but not that noone changed it).

The other alternative would be to load the entire file into memory. That's what I'd usually do.


Note that

open(FH, $qfn) or die "Cannot open file\n";

is better written as

open(my $FH, '<', $qfn)
   or die("Can't open file \"$qfn\": $!\n");
  • Three-arg open avoids some problems.
  • Including the error reason in the error message is beneficial.
  • Including the path in the error message is beneficial.
  • One should avoid DATA as Perl sometimes creates a handle by that name automatically.
  • One should avoid using global variables (e.g. FH) in favour or lexical variables (my $FH).