#function #perl
#функция #perl
Вопрос:
Для примера я читаю файл this, где после нескольких слов я проверяю, сколько «вас» присутствует.
Good
morning
to
you
May
every
step
you
make
you
be
filled
you
with
happiness
love
you
and
peace
you
Код, который я написал:
use warnings;
use strict;
my $log1_file = "log.log";
my $you_count = 0;
my $you_make_count = 0;
my $you_love_count = 0;
my $point ;
open(IN1, "<$log1_file" ) or die "Could not open file $log1_file: $!";
while (my $line = <IN1>) {
$point =$.;
print "$. main whilen";
my @fields = split' ',$line;
if ($fields[0] eq "Good") {
print "$. after good_ifn";
good_check();
print "$. after good_calln";
seek (IN1,$point,0);
#$. = $point;
print "$. n";
}
elsif ($fields[0] eq "make") {
print "$. after make_ifn";
make_check();
#$. = $point;
seek (IN1,$point,0);
}
elsif ($fields[0] eq "love") {
print "$. after love_ifn";
love_check();
#$. = $point;
seek (IN1,$point,0);
}
}
print "$you_countn";
print "$you_make_countn";
print "$you_love_countn";
close IN1;
sub love_check{
while (my $line = <IN1>)
my @fields = split' ',$line;
if ($fields[0] eq "you") {
$you_love_count ;
}
}
}
sub make_check{
while (my $line = <IN1>) {
my @fields = split' ',$line;
if ($fields[0] eq "you") {
$you_make_count ;
}
}
}
sub good_check{
while (my $line = <IN1>) {
my @fields = split' ',$line;
if ($fields[0] eq "you") {
$you_count ;
}
}
}
Если я использую seek (IN1,$point,0);
, чтобы указать обратно на местоположение, я получаю вывод, как показано ниже:
1 main while
1 after good_if
20 after good_call
20
21 main while
22 main while
23 main while
24 main while
25 main while
26 main while
27 main while
28 main while
29 main while
29 after make_if
41 main while
42 main while
43 main while
44 main while
44 after make_if
56 main while
Use of uninitialized value $fields[0] in string eq at check.pl line 15, <IN1> line 56.
Use of uninitialized value $fields[0] in string eq at check.pl line 25, <IN1> line 56.
Use of uninitialized value $fields[0] in string eq at check.pl line 33, <IN1> line 56.
57 main while
58 main while
59 main while
60 main while
61 main while
62 main while
63 main while
63 after love_if
68 main while
69 main while
70 main while
70 after love_if
75 main while
76 main while
76 after love_if
81 main while
82 main while
82 after love_if
87 main while
Use of uninitialized value $fields[0] in string eq at check.pl line 15, <IN1> line 87.
Use of uninitialized value $fields[0] in string eq at check.pl line 25, <IN1> line 87.
Use of uninitialized value $fields[0] in string eq at check.pl line 33, <IN1> line 87.
88 main while
89 main while
90 main while
91 main while
6
8
8
Значения «you» в final верны, но не получают номера строк, как обычно.
И если я использую $. = $point;
только первый подраздел, все работает нормально.
Может ли кто-нибудь сказать мне лучший способ указать то же местоположение?
Комментарии:
1. Проверьте
tell()
, хотите ли выseek()
чего-то. perldoc.perl.org/functions/tell2. Логика ваших подсчетов отключена. Вашим «хорошим» количеством будут все «вы» в файле, в «make» будут все, за вычетом тех, которые находятся между «Good» и «make» и т.д. Обращение файла назад и вперед редко является правильным ответом. Вы должны сказать, чего вы пытаетесь достичь, а затем спросить, есть ли лучшее решение, чем это.
3. Я удивлен и удивлен, что определенный модуль не был упомянут. weeeee!
Ответ №1:
Этот вопрос очень похож на проблему XY. Или домашнее задание. Логика подсчета «вы», связанных с определенными ключевыми словами, кажется произвольной. Например, «вы» после «Хорошо» будет содержать все «вы» в других словах вместе взятых.
Поскольку я предполагаю, что это своего рода учебное упражнение, я прокомментирую ваш код, а затем предложу предлагаемое решение.
open(IN1, "<$log1_file" ) or die "Could not open file $log1_file: $!";
Всегда используйте три аргумента open с явным открытым режимом, чтобы избежать внедрения кода. Используйте лексический дескриптор файла ( my $fh
) вместо глобального простого слова ( IN1
). Это должно выглядеть так:
open my $fh, "<", $log1_file or die "Could not open '$log1_file': $!";
Это разделение не требуется
my @fields = split' ',$line;
Поскольку у вас есть только одно слово в каждой строке, все, что это делает, это удаляет новую строку в конце (потому что разделение на ' '
— это особый случай). Если вы хотите удалить новую строку, вы должны использовать chomp
, например: chomp($line)
Использование seek
и tell
для навигации по вашему файлу, вероятно, является неправильным решением. Хотя вы можете заставить это работать, есть лучшие решения.
Использование трех почти идентичных подпрограмм для выполнения одного и того же (почти) — плохая практика, IMO. Использование глобальных переменных внутри подпрограмм также не очень хорошо. То, что вы должны искать, это инкапсуляция: передайте необходимую информацию подпрограмме, а затем верните нужные значения. Например:
my @file = <$fh> # slurp the file into an array
....
if (/^Good$/) {
$good_count = you_check($line_number);
} elsif (/^make$/) {
$make_count = you_check($line_number);
} ....etc
sub you_check {
my $line_number = shift;
my $count = 0;
for my $line ($file[$line_number] .. $file[$#file]) {
$count if $line =~ /^you$/;
}
return $count;
}
Предполагая, что мы оставим @file
неизменным, you_check()
функцию можно использовать, не беспокоясь о том, что ее использование изменит что-то еще.
С учетом сказанного, если бы я должен был решить эту задачу, я бы использовал хэш. Это позволит вам динамически определять ключевое слово и добавлять новые ключевые слова без необходимости добавлять много нового кода.
use strict;
use warnings;
use Data::Dumper;
my %count;
my $key;
while (<>) {
if (/(Good|make|love)/) {
$key = $1;
}
if (/you/) {
$count{$key} if $key;
}
}
print Dumper %count;
Используйте это так в командной строке:
$ count.pl log.log
Вывод при использовании с вашими образцами данных:
$VAR1 = {
'love' => 2,
'Good' => 2,
'make' => 2
};
Если вы все еще хотите сохранить правило, согласно которому первое количество слов содержит все остальные слова, вы можете отслеживать, какое слово идет первым, а затем просто добавлять количество после этого. Использование хэша для этого количества можно масштабировать для любого количества слов, которое вы хотите отслеживать.
Ответ №2:
Файлы представляют собой потоки байтов. Они вообще не понимают строки. Символ в конце строки — это просто еще один символ в потоке.
Поэтому, когда в документации для seek()
говорится о «позиции дескриптора файла», это означает количество байтов в файле, а не количество строк. Таким seek
образом, редактирование номера строки никогда не даст желаемого результата.
Вместо этого вы должны использовать tell()
функцию, чтобы узнать, в какой позиции вы находитесь в файле, а затем использовать это значение в качестве параметра, который вы отправляете seek()
.
Это будет работать примерно так:
# Lexical filehandle and three-arg open()
open my $in_fh, '<', $log1_file or die ...;
my $pos = tell $in_fh;
while (my $line = <$in_fh>) {
# Do various stuff.
# If you want to go back to the start of the line.
seek $in_fh, $pos, 0;
# Get the pos at the end of the current line
$pos = tell $in_fh;
}
Обновление: если вы хотите иметь возможность перейти к любой строке в файле, то одним из подходов может быть чтение файла один раз и вызов tell()
в начале каждой строки. Затем вы можете сохранить эти значения в массиве, который вы можете использовать для перехода к началу любой строки.