Используйте Perl для подсчета вхождений всех слов в файле или во всех файлах в каталоге

Поэтому я пытаюсь написать скрипт на Perl, который будет принимать 3 аргумента.

  1. Первый аргумент - это входной файл или каталог.
    • Если это файл, он посчитает количество вхождений всех слов
    • Если это каталог, он будет рекурсивно проходить через каждый каталог и получать все количество вхождений для всех слов в файлах в этих каталогах.
  2. Второй аргумент - это число, которое будет отображать количество слов, отображаемых с наибольшим числом вхождений.
    • Это выведет на консоль только номер для каждого слова
  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] );
 }

Третья часть печати в файл, неясно, что такое "они" (слова, число или оба; ограничено верхними или всеми словами) из вашего вопроса. Я оставлю это усилие для вас, чтобы открыть файл, распечатать информацию в файл и закрыть файл.

Другие вопросы по тегам