unix - 文件中有多少个字符出现次数的细分

时间:2012-04-15 03:06:25

标签: linux perl bash shell unix

是否有一个内置命令来执行此操作,或者有任何人对运行它的脚本有任何好运?

我希望了解有多少记录(由特定的EOL定义,例如“^%!”)有多少次出现的特定字符。 (按出现次数降序排列)

例如,使用此示例文件:

jdk,|ljn^%!dk,|sn,|fgc^%!
ydfsvuyx^%!67ds5,|bvujhy,|s6d75
djh,|sudh^%!nhjf,|^%!fdiu^%!

建议输入:将EOL和文件名分隔为参数。

bash/perl some_script_name ",|" "^%!" samplefile

期望的输出:

occs    count
3        1
2        1
1        2
0        2

这是因为第1条记录有一个分隔符,第2条记录有2条,第3条记录有0条,第4条记录有3条,第5条记录有1条,第6条记录有0条。

如果您可以使分隔符和EOL参数接受十六进制输入(即2C7C)或正常字符输入(即|),则可获得奖励。

4 个答案:

答案 0 :(得分:2)

<强> 脚本:

#!/usr/bin/perl
use strict;

$/ = $ARGV[1];
open my $fh, '<', $ARGV[2] or die $!;
my @records = <$fh> and close $fh;

$/ = $ARGV[0];
my %counts;
$counts{(split $_)-1}++ for @records;
delete $counts{-1};

print "$_\t$counts{$_}\n" for (reverse sort keys %counts);

<强> 测试:

perl script.pl ',|' '^%!' samplefile 

<强> 输出:

3   1
2   1
1   2
0   2

答案 1 :(得分:0)

这就是perl的生活:

#!perl -w
use 5.12.0;

my ($delim, $eol, $file) = @ARGV;

open my $fh, "<$file" or die "error opening $file $!";
$/ = $eol; # input record separator

my %counts;
while (<$fh>) {
    my $matches = () = $_ =~ /(\Q$delim\E)/g; # "goatse" operator
    $counts{$matches}++;
}

say "occs\tcount";
foreach my $num (reverse sort keys %counts) {
    say "$num\t$counts{$num}";
}

(如果您还没有获得5.12,请删除“use 5.12”行并将say替换为print

答案 2 :(得分:0)

嗯,文件末尾还有一个空记录,其中有0个。所以,这是一个可以做你想要的脚本。添加标题和以其他方式调整printf输出仍然是一个练习。 :)

基本上,读取整个文件,将其拆分为记录,对于每个记录,使用/ g正则表达式来计算子分隔符。由于/ g返回所有匹配的数组,因此使用@ {[]}生成一个arrayref,然后在标量上下文中对其进行deref以获取计数。对问题的这个特定部分必须有一个更优雅的解决方案,但无论如何;这是perl线路噪音。 ;)

user@host[/home/user]
$ ./test.pl ',|' '^%!' test.in
3   1
2   1
1   2
0   3
user@host[/home/user]
$ cat test.in
jdk,|ljn^%!dk,|sn,|fgc^%!
ydfsvuyx^%!67ds5,|bvujhy,|s6d75
djh,|sudh^%!nhjf,|^%!fdiu^%!
user@host[/home/user]
$ cat test.pl
#!/usr/bin/perl

my( $subdelim, $delim, $in,) = @ARGV;
$delim = quotemeta $delim;
$subdelim = quotemeta $subdelim;
my %counts;

open(F, $in) or die qq{Failed opening $in: $?\n};
foreach( split(/$delim/, join(q{}, <F>)) ){
  $counts{ scalar(@{[m/.*?($subdelim)/g]}) }++;
}
printf( qq{%i% 4i\n}, $_, $counts{$_} ) foreach (sort {$b<=>$a} keys %counts);

这是一个修改后的版本,只保留包含至少一个非空格字符的字段。这会删除最后一个字段,但也会删除任何其他空字段。它还使用$ /和\ Q \ E来减少一些显式函数调用(谢谢Alex)。并且,与前一个一样,它适用于严格+警告;

#!/usr/bin/perl

my( $subdelim, $delim, $in ) = @ARGV;
local $/=$delim;

my %counts;
open(F, $in) or die qq{Failed opening $in: $?\n};
foreach ( grep(/\S/, <F>) ){
  $counts{ scalar(@{[m/.*?(\Q$subdelim\E)/g]}) }++;
}
printf( qq{%i% 4i\n}, $_, $counts{$_} ) foreach (sort {$b<=>$a} keys %counts);

如果你真的只想无条件删除最后一条记录,我会偏爱使用pop:

#!/usr/bin/perl

my( $subdelim, $delim, $in ) = @ARGV;
local $/=$delim;

my %counts;
open(F, $in) or die qq{Failed opening $in: $?\n};
my @lines = <F>;
pop @lines;
$counts{ scalar(@{[m/.*?(\Q$subdelim\E)/g]}) }++ foreach (@lines);
printf( qq{%i% 4i\n}, $_, $counts{$_} ) foreach (sort {$b<=>$a} keys %counts);

答案 3 :(得分:0)

awk中的解决方案:

BEGIN {
    RS="\\^%!"
    FS=",\\|"
    max_occ = 0
} 
{
    if(match($0, "^ *$")) {  # This is here to deal with the final separator.
        next
    }

    if(NF - 1 > max_occ) {
        max_occ = NF - 1
    }
    count[NF - 1]=count[NF - 1] + 1
}
END {
    printf("occs    count\n")
    for(i = 0; i <= max_occ; i++) {
        printf("%s    %s\n", i, count[i])
    }
}