Несколько файловых дескрипторов, открывающих один и тот же файл - это хорошая практика? - PullRequest
6 голосов
/ 30 января 2020

У меня есть файл grades.tsv с тремя столбцами, в которых отображаются имена, предметы и оценки учащихся:

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

Я хотел рассчитать среднюю оценку для каждого учащегося и добавить ее в отдельный столбец. Для этого я использовал два дескриптора файла DATA и OUT, открывающие один и тот же файл:

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;

Код работает, но я не уверен, что это правильный путь для этой задачи. Это хорошая практика или есть лучшие способы, которые следует отдавать предпочтение?

Ответы [ 3 ]

7 голосов
/ 30 января 2020

Повторное открытие файла в порядке. Одной из альтернатив может быть поиск в начале файла.

use Fcntl qw( SEEK_SET );

seek(DATA, 0, SEEK_SET);

Поиск более эффективен, поскольку не нужно проверять разрешения, и т. Д. c. Это также гарантирует, что вы получите тот же файл (но никто не изменил его).

Другой альтернативой будет загрузка всего файла в память. Это то, что я обычно делаю.


Обратите внимание, что

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

лучше записать как

open(my $FH, '<', $qfn)
   or die("Can't open file \"$qfn\": $!\n");
  • Three-arg open позволяет избежать некоторых проблем.
  • Включение причины ошибки в сообщение об ошибке полезно.
  • Включение пути в сообщение об ошибке полезно.
  • Следует избегать DATA, поскольку Perl иногда создает дескриптор с этим именем автоматически.
  • Следует избегать использования глобальных переменных (например, FH) в пользу или лексических переменных (my $FH).
4 голосов
/ 31 января 2020

Есть еще одна вещь, которую следует учитывать при такого рода операциях. Что вы делаете, если вы запутались при записи новых данных? Как вы собираетесь терпеть программу, которая усекает исходный файл, но не может полностью записать новые данные?

Вместо того, чтобы открывать дескриптор файла записи с тем же именем файла, используйте временный файл. File :: Temp является частью стандартной библиотеки:

use File::Temp;
my( $temp_fh, $tempfile ) = tempfile();

Теперь запишите все в $temp_fh, пока не убедитесь, что вы смогли завершить вывод. После этого используйте rename, чтобы переместить готовый файл на место:

rename $tempfile => $original;

Шон также правильно указывает, что это изменит индекс, тем самым нарушая жесткие ссылки. Вы можете скопировать новый файл в старый, если это имеет значение для вас, но я редко видел ситуацию, когда технология была настолько продвинутой:)

Если вы запутались, исходные данные все еще там, и вы можно попробовать еще раз. Примечание: это предполагает, что два файла находятся в одном разделе, поскольку это требование rename.

. Хотя это может не иметь значения в вашем случае, вы также должны учитывать, что другие потребители делают в это время. берет вас, чтобы написать новый файл. Что произойдет, если другая программа захочет прочитать исходный файл сразу после того, как вы его урезали, но не записали данные (или написали не полностью)? Как правило, вы хотите убедиться, что файл завершен, прежде чем он станет доступен для других программ.

Если вам не нравится временный файл, есть другие способы решения проблемы. Переместите исходный файл на имя резервной копии, затем прочитайте его и запишите исходное имя. Или введите другое имя файла и переместите его на место. См., Например, Perl настройки -i переключателя командной строки только для этой проблемы.

3 голосов
/ 31 января 2020

Пример кода для табеля успеваемости студента

#!/usr/bin/perl
#
# USAGE:
#   prog.pl
#
# Description:
#   Demonstration code for StackOverflow Q59991322
#
# StackOverflow: 
#   Question 59991322
#
# Author:
#   Polar Bear    https://stackoverflow.com/users/12313309/polar-bear
#
# Date: Tue Jan 30 13:37:00 PST 2020
#

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

use Data::Dumper;

my $debug = 0;      # debug flag
my %hash;
my $student;
my ($subject,$mark);

map{
    chomp;
    my($name,$subject,$mark) = split "\t",$_;
    $hash{$name}{subjects}{$subject} = $mark;
    $hash{$name}{compute}{Total} += $mark;
    $hash{$name}{compute}{Num_subjects}++;
} <DATA>;

say Dumper(\%hash) if $debug;

foreach $student ( sort keys %hash ) {
    $hash{$student}{compute}{GPA} = $hash{$student}{compute}{Total}/$hash{$student}{compute}{Num_subjects};
    $~ = 'STDOUT_REPORT';
    write;
    print_marks($student);
    $~ = 'STDOUT_REPORT_END';
    write;
}

sub print_marks {
    my $student = shift;

    $~ = 'STDOUT_MARKS';

    while( ($subject,$mark) = each %{$hash{$student}{subjects}} ) {
        write;
    }
}

format STDOUT_REPORT = 
+----------------------------+
| Student: @<<<<<<<<<<       |
$student
+----------------------------+
.

format STDOUT_REPORT_END =
+----------------------------+
| Subjects taken:     @<<    |
$hash{$student}{compute}{Num_subjects}
| Grade average:      @<<    |
$hash{$student}{compute}{GPA}
+----------------------------+

.

format STDOUT_MARKS =
| @<<<<<<<<<<<<<<     @<<    |
$subject, $mark
.

__DATA__
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

Вывод

+----------------------------+
| Student: Aria              |
+----------------------------+
| Mathematics         8      |
| History             7      |
| Geography           6      |
+----------------------------+
| Subjects taken:     3      |
| Grade average:      7      |
+----------------------------+

+----------------------------+
| Student: Isabella          |
+----------------------------+
| Music               8      |
| Mathematics         9      |
| History             4      |
| English             5      |
| Geography           7      |
+----------------------------+
| Subjects taken:     5      |
| Grade average:      6.6    |
+----------------------------+

+----------------------------+
| Student: Liam              |
+----------------------------+
| Geography           8      |
| English             8      |
| History             6      |
| Mathematics         5      |
+----------------------------+
| Subjects taken:     4      |
| Grade average:      6.7    |
+----------------------------+
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...