使用Perl计算文件或目录中所有文件中所有单词的出现次数

时间:2014-09-26 00:00:44

标签: regex perl perlscript

所以我试着编写一个Perl脚本,它将接受3个参数。

  1. 第一个参数是输入文件或目录。
    • 如果是文件,则会计算所有单词的出现次数
    • 如果是目录,它将以递归方式遍历每个目录,并获取这些目录中文件中所有单词的出现次数
  2. 第二个参数是一个数字,它将显示具有最高出现次数的单词数。
    • 这将仅向控制台打印每个单词的编号
  3. 将它们打印到输出文件,该文件是命令行中的第三个参数。
  4. 它似乎正在递归地搜索目录并查找文件中所有出现的单词并将它们打印到控制台。

    如何将这些打印到输出文件中,以及如何获取第二个参数,即数字,例如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 个答案:

答案 0 :(得分:0)

这将总计在命令行上给出的目录或文件中出现的单词:

#!/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

<强>注意事项

  • 您应该为文件mimetypes,可读性,等添加测试。
  • 注意unicode
  • 写入文件只需将> filename.txt添加到命令行的末尾; - )
  • IO::All不是标准的CORE IO软件包,我只是在这里宣传并推广它;-)(你可以把那个位换掉)
  • 如果您想添加sort_by选项(-n --numeric-a --alphabetic ),Sort::Maker可能是提高可管理性的一种方法。

编辑忽略了在OP请求时添加选项。

答案 1 :(得分:0)

我建议重组你的程序/脚本。你发布的内容很难理解。一些评论可能有助于了解正在发生的事情。我将尝试通过一些代码片段来解释如何安排事情,希望有助于解释项目。我将介绍您在问题中列出的三个项目。

由于第一个参数可以是文件或目录,我会使用-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调用处理文件列表之后)。当你有一个目录时,你用路径调用搜索(就像你现在正在做的那样),推送你当前阵列上的元素,比如

# 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] );
 }

打印到文件的第三部分,目前还不清楚是什么&#34;他们&#34;从你的问题是(单词,计数或两者;仅限于顶部或全部单词)。我将为您打开一个文件,将信息打印到文件并关闭文件。

答案 2 :(得分:0)

我已经弄清楚了。以下是我的解决方案。我不确定这是否是最佳方式,但它确实有效。

    # 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);
          }
       }
    }