如何选择特定的行范围并计算第一个字符与每个唯一的第二个字符的具体出现次数?

如何选择特定的行范围并计算第一个字符与每个唯一的第二个字符的具体出现次数?

嗨,我有一个这样的文件:

#0
0:()
1b:cg*b
1c
0:cg
xe
#4
0:()
0b:cg*b
xc
0:cg
1e
#8
0:()
0b:cg*b
xc
0:cg
xe
#12
1b:cg*b
xc
0:cg
0e
#16
xb:cg*b
1c
xe
#20
1:()
xb:cg*b
xc
1:cg
1e
#24
x:()
xb:cg*b
xc
xe
#28
0:()
1b:cg*b
0c
x:cg
0e
#29
0:()
0b:cg*b
1c
x:cg
xe
#32
0:()
1b:cg*b

其中#0表示时间 0 ,#8表示时间 8 等。现在我想根据给定的时间范围(如 2 到 30)打印该文件的部分(我想手动输入)。

在此文件中,时间 2 和 30 不存在,因此输出应该是从下一次 2 之后的时间 (#4) 到下一次 30 之后的时间 (#32) 之前的行(结果 temp1 = 第 7 行到 50 行)

temp1 输出应该是这样的:

#4
0:()
0bd*b
xc
0:cg
1e
#8
0:()
0bd*b
xc
0:cg
xe
#12
1bd*b
xc
0:cg
0e
#16
xbd*b
1c
xe
#20
1:()
xbd*b
xc
1:cg
1e
#24
x:()
xbd*b
xc
xe
#28
0:()
1bd*b
0c
x:cg
0e
#29
0:()
0bd*b
1c
x:cg
xe

这里 :() 、 bd*b 、 c 、 :cg 、 e 是第 2 列第一个字符后的字符串。 0 、 1 、 x 是第一个字符

现在 temp2 输出应该是这样的:

        4 8 12 16 20 24 28 29
:()     0 0 -  -  1  x  0   0
b:cg*b  0 0 1  x  x  x  1   0
c       x x x  1  x  x  0   1
:cg     0 0 0  -  1  -  x   x
e       1 x 0  x  1  x  0   x

现在我必须数数。 temp2 第 1 列中每个条目的 x 输出其规则:

  1. 只考虑 x 之前有 0 或 1
  2. 不要计算 x 之前还有另一个 x
  3. 如果 ax 出现在时间范围的开始处,则对其进行计数
  4. 如果 temp2 输出中存在除 0,1,x 之外的任何其他字符,则应忽略此字符。

所以,最终的输出应该是这样的:

name     count x
:()      1     x
b:cg*b   1     x
c        2     x
:cg      1     x
e        4     x

注意:无论如何,我只想要最终输出,我不需要保留中间临时输出文件,但如果保留临时文件,那么这对我来说将是一个优势。显然,如果输入文件中存在任何空行,我不希望任何输出文件中出现任何空行,则应将其删除)

我是脚本编写新手,我编写了一个很长的 tcl 脚本,但它的运行时间很长,所以我想要一个 awk 或 sed 解决方案。

答案1

这个 Perl 脚本将一次性完成您想要的操作:

#!/usr/bin/env perl
use strict;
use Getopt::Std;

## This hash will hold the options
my %opts;

## Read the options
getopts('t:s:e:',\%opts) || do { print "Invalid option\n"; exit(1); };

## Keep the temp file if the script is run 
## with -t
my $keep_temp_file=$opts{t}||undef;

## The temp file's file handle
my $tmp;
## The temp file
my $temp_file=`mktemp`;
chomp($temp_file);
## Read the time range
my $start=$opts{s}||undef;
my $end=$opts{e}||undef;


## Open the input file
open($tmp,'<',"$ARGV[0]")|| 
    die("Need an input file as the 1st argument: $!\n");

my ($time,$want);
my (%data,%letters);
## Read the input file
line:while (<$tmp>) {
    ## skip blank lines
    next if /^\s*$/;

    ## remove trailing newlines
    chomp;
    ## Is this line one of the start times?
    if (/^#(\d+)/) {
        if ($1>=$start && $1<=$end) {
            $time=$1;
            $want=1;
        } elsif ($1>=$end) {
            $want=0;
            last line;
        }
    }
    ## If we want this line, save it in
    ## the %data hash.
    if ($want==1) {
        ## Skip if this line is the one that has the time
        ## definition.
        next if /^#/;
        ## Get the two characters of the line
        /^(.)(.+)/;
        $data{$time}{$2}=$1;
        ## Save each letter seen
        $letters{$2}++;
    }  
}
## Once the file has been processed, create
## the temp file.
open($tmp,'>',$temp_file)|| 
    die("Could not open temp file $temp_file for writing: $!\n");

my @times=sort {$a <=> $b } keys(%data);
print $tmp " ";
printf $tmp "%6s", "$_" for @times;
print $tmp "\n";
foreach my $letter (sort keys(%letters)) {
    print $tmp "$letter " ;
    foreach my $time (@times) {
        defined $data{$time}{$letter} ? 
            printf $tmp "%6s","$data{$time}{$letter} " : printf $tmp "%6s","- ";
    }
    print $tmp "\n";
}
close($tmp);
## Process the tmp file to get your desired output
open(my $fh,'<',"$temp_file")|| 
    die("Could not open temp file $temp_file for reading: $!\n");
## Print the header
printf "%-7s%6s%10s\n",'name', 'count', 'x';
while (<$fh>) {
    ## Skip first line
    next if $.==1;

    ## Collect the columns
    my @foo=split(/\s+/);
    ## get the letter
    my $let=shift(@foo);
    my $c=0;
    ## Check if the first one is an x
    $c++ if $foo[0] eq 'x';
    ## Check the rest
    for (my $i=1;$i<=$#foo;$i++) {
        ## Get the previous position. This is complicated
        ## since you want to ignore the non [01x] characters
        my $prev="";
        for (my $k=$i-1; $k>-1; $k--) {
            if ($foo[$k]=~/^[01x]$/) {
                $prev=$foo[$k];
                last;
            }
        }
        ## If this is an x, increment c if 
        ## the previous character was 0 or 1
        if ($foo[$i] eq 'x' && ($prev=~/^[01]$/ || $prev=~/^$/)) {
            $c++;
        }
    } 
    printf "%-7s%6s%10s\n", $let,$c,"x";
}
## If we want to keep the temp file, copy
## it to the file name given.
if ($keep_temp_file) {
    system("cp $temp_file $keep_temp_file");
}
## else, delete it
else {
    unlink($temp_file);
}

如果将其另存为foo.pl,则可以像这样运行:

foo.pl -s 2 -e 30 -t 2-30.temp file 

设置-s开始时间,设置-e结束时间。如果您希望保留临时文件,请为其指定一个带有-t.如果没有-t,临时文件将被删除。

在你的例子中,它会产生:

$ perl foo.pl -s 2 -e 30 -t aa file2
name    count         x
:()         1         x
:cg         1         x
b:cg*b      1         x
c           2         x
e           4         x

我回答这个问题是因为这是一个有趣的问题,而你是新来的。但是,请注意,我们不是脚本编写服务。要求如此复杂的解决方案的问题是题外话。我们很乐意帮助您解决特定问题,但我们(通常)不会为您编写整个脚本。

下次,开始写一些东西并将你面临的问题分开。问一个具体的对每个问题提出一个问题,您可以这样构建您的脚本。

相关内容