Используйте Perl для подсчета вхождений всех слов в файле или во всех файлах в каталоге
Поэтому я пытаюсь написать скрипт на Perl, который будет принимать 3 аргумента.
- Первый аргумент - это входной файл или каталог.
- Если это файл, он посчитает количество вхождений всех слов
- Если это каталог, он будет рекурсивно проходить через каждый каталог и получать все количество вхождений для всех слов в файлах в этих каталогах.
- Второй аргумент - это число, которое будет отображать количество слов, отображаемых с наибольшим числом вхождений.
- Это выведет на консоль только номер для каждого слова
- Выведите их в выходной файл, который является третьим аргументом в командной строке.
Похоже, что он работает до рекурсивного поиска по каталогам и поиска всех вхождений слов в файле и выводит их на консоль.
Как я могу напечатать их в выходной файл, а также, как мне взять второй аргумент, который является числом, скажем, 5, и сделать так, чтобы он выводил на консоль число слов с наибольшим количеством вхождений при печати слов на выход? файл?
Вот то, что я имею до сих пор:
#!/usr/bin/perl -w
use strict;
search(shift);
my $input = $ARGV[0];
my $output = $ARGV[1];
my %count;
my $file = shift or die "ERROR: $0 FILE\n";
open my $filename, '<', $file or die "ERROR: Could not open file!";
if ( -f $filename ) {
print("This is a file!\n");
while ( my $line = <$filename> ) {
chomp $line;
foreach my $str ( $line =~ /\w+/g ) {
$count{$str}++;
}
}
foreach my $str ( sort keys %count ) {
printf "%-20s %s\n", $str, $count{$str};
}
}
close($filename);
if ( -d $input ) {
sub search {
my $path = shift;
my @dirs = glob("$path/*");
foreach my $filename (@dirs) {
if ( -f $filename ) {
open( FILE, $filename ) or die "ERROR: Can't open file";
while ( my $line = <FILE> ) {
chomp $line;
foreach my $str ( $line =~ /\w+/g ) {
$count{$str}++;
}
}
foreach my $str ( sort keys %count ) {
printf "%-20s %s\n", $str, $count{$str};
}
}
# Recursive search
elsif ( -d $filename ) {
search($filename);
}
}
}
}
3 ответа
Я понял это. Следующее моё решение. Я не уверен, что это лучший способ сделать это, но это работает.
# Check if there are three arguments in the commandline
if (@ARGV < 3) {
die "ERROR: There must be three arguments!\n";
exit;
}
# Open the file
my $file = shift or die "ERROR: $0 FILE\n";
open my $fh,'<', $file or die "ERROR: Could not open file!";
# Check if it is a file
if (-f $fh) {
print("This is a file!\n");
# Go through each line
while (my $line = <$fh>) {
chomp $line;
# Count the occurrences of each word
foreach my $str ($line =~ /\b[[:alpha:]]+\b/) {
$count{$str}++;
}
}
}
# Check if the INPUT is a directory
if (-d $input) {
# Call subroutine to search directory recursively
search_dir($input);
}
# Close the file
close($fh);
$high_count = 0;
# Open the file
open my $fileh,'>', $output or die "ERROR: Could not open file!\n";
# Sort the most occurring words in the file and print them
foreach my $str (sort {$count{$b} <=> $count{a}} keys %count) {
$high_count++;
if ($high_count <= $num) {
printf "%-31s %s\n", $str, $count{$str};
}
printf $fileh "%-31s %s\n", $str, $count{$str};
}
exit;
# Subroutine to search through each directory recursively
sub search_dir {
my $path = shift;
my @dirs = glob("$path/*");
# Loop through filenames
foreach my $filename (@dirs) {
# Check if it is a file
if (-f $filename) {
# Open the file
open(FILE, $filename) or die "ERROR: Can't open file";
# Go through each line
while (my $line = <FILE>) {
chomp $line;
# Count the occurrences of each word
foreach my $str ($line =~ /\b[[:alpha:]]+\b/) {
$count{$str}++;
}
}
# Close the file
close(FILE);
}
elsif (-d $filename) {
search_dir($filename);
}
}
}
Это будет суммировать вхождения слов в каталоге или файле, заданном в командной строке:
#!/usr/bin/env perl
# wordcounter.pl
use strict;
use warnings;
use IO::All -utf8;
binmode STDOUT, 'encoding(utf8)'; # you may not need this
my @allwords;
my %count;
die "Usage: wordcounter.pl <directory|filename> number \n" unless ~~@ARGV == 2 ;
if (-d $ARGV[0] ) {
push @allwords, $_->slurp for io($ARGV[0])->all_files;
}
elsif (-f $ARGV[0]) {
@allwords = io($ARGV[0])->slurp ;
}
while (my $line = shift @allwords) {
foreach ( split /\s+/, $line) {
$count{$_}++
}
}
my $count_to_show;
for my $word (sort { $count{$b} <=> $count{$a} } keys %count) {
printf "%-30s %s\n", $word, $count{$word};
last if ++$count_to_show == $ARGV[1];
}
Изменяя sort
и / или io
звонки можно sort { }
по количеству вхождений, в алфавитном порядке по слову, либо для файла, либо для всех файлов в каталоге. Эти параметры будет довольно легко добавить в качестве параметров. Вы также можете отфильтровать или изменить способ определения слов для включения в %count
хеш при изменении foreach ( split /\s+/, $line)
сказать, включить соответствие / фильтр, такой как foreach ( grep { length le 5 } split /\s+/, $line)
чтобы считать только слова из пяти или менее букв.
Пример запуска в текущем каталоге:
./wordcounter ./ 10
the 116
SV 87
i 66
my_perl 58
of 54
use 54
int 49
PerlInterpreter 47
sv 47
Inline 47
return 46
Предостережения
- вам, вероятно, следует добавить тест для файловых типов, читабельности и т. д.
- обратите внимание на юникод
- для записи в файл просто добавьте
> filename.txt
до конца вашей командной строки;-) IO::All
это не стандартный пакет CORE IO, я только рекламирую и продвигаю его здесь;-) (вы можете поменять это)- Если вы хотите добавить
sort_by
вариант (-n --numeric
,-a --alphabetic
и т. д.)Sort::Maker
может быть одним из способов сделать это управляемым.
РЕДАКТИРОВАТЬ пренебрегли, чтобы добавить параметры, как запросили OP.
Я бы предложил реструктурировать вашу программу / скрипт. Трудно следить за тем, что вы опубликовали. Несколько комментариев могут быть полезны, чтобы следить за тем, что происходит. Я попытаюсь разобраться, как бы я устроил вещи с помощью некоторых фрагментов кода, которые, как мы надеемся, помогут объяснить вещи. Я рассмотрю три вопроса, которые вы изложили в своем вопросе.
Поскольку первый аргумент может быть файлом или каталогом, я бы использовал -f и -d для проверки, чтобы определить, что является вводом. Я хотел бы использовать список / массив, чтобы содержать список файлов для обработки. Если бы это был только файл, я бы просто поместил его в список обработки. В противном случае я бы вызвал подпрограмму для возврата списка файлов, которые нужно обработать (аналогично вашей подпрограмме поиска). Что-то вроде:
# List file files to process
my @fileList = ();
# if input is only a file
if ( -f $ARGV[0] )
{
push @fileList,$ARGV[0];
}
# If it is a directory
elsif ( -d $ARGV[0] )
{
@fileList = search($ARGV[0]);
}
Таким образом, в вашей подпрограмме поиска вам нужен список / массив, в который нужно поместить элементы, которые являются файлами, а затем вернуть массив из подпрограммы (после того, как вы обработали список файлов из вызова glob). Когда у вас есть каталог, вы вызываете search с путем (так же, как вы это делаете в настоящее время), помещая элементы в ваш текущий массив, такие как
# If it is a file, save it to the list to be returned
if ( -f $filename )
{
push @returnValue,$filename;
}
# else if a directory, get the files from the directory and
# add them to the list to be returned
elsif ( -d $filename )
{
push @returnValue, search($filename);
}
После того, как у вас есть список файлов, циклически обрабатывайте каждый файл (открытие, чтение строк при закрытии, обработка строк для слов). Цикл foreach для обработки каждой строки работает правильно. Однако, если ваши слова имеют точки, запятые или другие знаки препинания, вы можете удалить эти элементы перед подсчетом слова в хэше.
В следующей части вы спросили об определении слов с наибольшим количеством. В этом случае вы хотите создать еще один хеш, который имеет ключ счетчиков (для каждого слова), и значение этого хеша представляет собой список / массив слов, связанных с этим количеством счетчиков. Что-то вроде:
# Hash with key being a number and value a list of words for that number
my %totals= ();
# Temporary variable to store occurrences (counts) of the word
my $wordTotal;
# $w is the words in the counts hash
foreach my $w ( keys %counts )
{
# Get the counts for the word
$wordTotal = $counts{$w};
# value of the hash is an array, so de-reference the array ( the @{ },
# and push the value of the counts array onto the array
push @{ $totals{$wordTotal} },$w; # the key to total is the value of the count hash
# for which the words ($w) are the keys
}
Чтобы получить слова с наибольшим количеством, вам нужно получить ключи из общего количества и перевернуть отсортированный список (отсортированный по численности), чтобы получить число N наибольшее. Поскольку у нас есть массив значений, нам нужно будет посчитать каждый выход, чтобы получить N наибольшего числа.
# Number of items outputted
my $current = 0;
# sort the total (keys) and reverse the list so the highest values are first
# and go through the list
foreach my $t ( reverse sort { $a <=> $b} keys %totals) # Use the numeric
# comparison in
# the sort
{
# Since each value of total hash is an array of words,
# loop through that array for the values and print out the number
foreach my $w ( sort @{$total{$t}}
{
# Print the number for the count of words
print "$t\n";
# Increment the number output
$current++;
# if this is the number to be printed, we are done
last if ( $current == $ARGV[1] );
}
# if this is the number to be printed, we are done
last if ( $current == $ARGV[1] );
}
Третья часть печати в файл, неясно, что такое "они" (слова, число или оба; ограничено верхними или всеми словами) из вашего вопроса. Я оставлю это усилие для вас, чтобы открыть файл, распечатать информацию в файл и закрыть файл.