模式匹配循环很慢,方法加快了



好的,所以我用bash编写了一个脚本,通过搜索用户名或IP地址来显示ftp连接的整个流程。我让它将数据读入数组,搜索条件,然后将该进程 ID 与其他进程 ID 匹配,以便获得整个流程。

然而,性能非常慢,根据专家交流社区中其他人的建议,我决定在perl中尝试一下。我试图尽可能多地学习,但还有很长的路要走。我正在尝试搜索条件,获取该行的进程 ID,然后将所有行读取到与该进程 ID 匹配的数组中,因此我基本上获得了 ftp 连接的整个流程。

假设我会从文件中读取每一行,对其进行模式匹配,如果它与我正在搜索的 IP 地址匹配,我会将该行复制到数组中。然后我想,在将这些行读入数组后,我将返回并从每一行中获取进程 ID,对文件进行另一次搜索,并将与进程 ID 匹配的所有行放入新数组中,然后打印出数组。

我有以下代码,用于根据文件是否与数组中的模式匹配文件行。

数组@pids具有以下数据,但比这多了几百个:

4682
4690
4692
4693
4696
5320

如果我正在阅读的行中有这个数字,那么我将其推送到一个新的数组。 一旦它到达文件的末尾,它就会返回到文件的开头,并致力于匹配数组@pids的下一个元素。 然后,我将新数组打印到一个文件中。

不幸的是,循环需要很长时间,有什么方法可以加快速度吗?我假设是因为我一直在一遍又一遍地浏览文件,使事情有点重复,但不确定我应该怎么做。

seek INPUT, 0, 0;
my @flow;
my $count = 0;
my $pid_count = 0;
foreach my $mPID(@pids){
    while(my $line = <INPUT>){
        if ($line =~ /$mPID/){
            push @flow, $line;
        }
    }
    push @flow, "###############n";
    seek INPUT, 0, 0;
}
open (OUTPUT, '>'.$output) or die "Couldn't read $output.n";
print OUTPUT @flow;
close (OUTPUT);

下面是来自以下数据的示例:

Dec  1 23:59:03 ftp1 ftpd[4152]: PASV
Dec  1 23:59:04 ftp1 ftpd[4152]: NLST
Dec  1 23:59:04 ftp1 ftpd[4152]: FTP session closed
Dec  1 23:59:05 ftp1 ftpd[4682]: USER test1
Dec  1 23:59:05 ftp1 ftpd[4682]: PASS password
Dec  1 23:59:08 ftp1 ftpd[4682]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prd

数据示例 我从以下位置获取与 IP 匹配的所有 pid:

Dec  1 23:59:08 ftp1 ftpd[4682]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prd
Dec  1 23:59:10 ftp1 ftpd[4690]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prod1
Dec  1 23:59:10 ftp1 ftpd[4692]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prod
Dec  1 23:59:11 ftp1 ftpd[4693]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  1 23:59:14 ftp1 ftpd[4696]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  1 23:59:40 ftp1 ftpd[5320]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  1 23:59:47 ftp1 ftpd[5325]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prd
Dec  1 23:59:48 ftp1 ftpd[5328]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prod1
Dec  1 23:59:49 ftp1 ftpd[5329]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prod
Dec  1 23:59:49 ftp1 ftpd[5330]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  2 00:00:09 ftp1 ftpd[9876]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  2 00:00:25 ftp1 ftpd[12830]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], test1
Dec  2 00:00:25 ftp1 ftpd[12832]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prd
Dec  2 00:00:27 ftp1 ftpd[12850]: FTP LOGIN FROM 192.168.0.2 [192.168.0.2], prod1

谢谢!

只要循环中有一个循环,这可能是性能问题。 假设您有 1000 个 pid 和 100 万行日志文件。 循环每个 pid 在文件中的每一行是 1000 * 100 万,即 10 亿美元!! 犯 错。。。迭 代。

现在,您正在检查每行是否包含每个PID。 如果你是手工做的,你就不会那样做。 您可以扫描该行以查找看起来像PID的东西,然后查看它们是否在您的列表中。 PID很容易识别,它们是整数,所以让我们这样做。 我们可以从简单开始,只需匹配数字即可。

#!/usr/bin/perl
use strict;
use warnings;
use autodie;
# Some test PIDs
my @pids = (
    12,
    1123,
    1234
);
# Put the PIDs into a hash.  Each line which matches will be stored.
my %pids = map { $_ => [] } @pids;
# Loop through the lines
while(my $line = <DATA>) {
    # Look for a PID
    if(my($pid) = $line =~ m{ [ s*(d+) ]: }x) {
        # Push it into the appropriate PID slot if it's on our list
        push @{$pids{$pid}}, $line if $pids{$pid};
    }
}
# Output the PIDs which have matching lines
for my $pid (keys %pids) {
    my $lines = $pids{$pid};
    next if !@$lines;
    print "PID: $pidn";
    print @$lines;
    print "##################n";
}

# Some test lines
__DATA__
Dec  1 23:59:03 ftp1 ftpd[  12]: PASV
Dec  1 23:59:04 ftp1 ftpd[1123]: NLST
Dec  1 23:59:04 ftp1 ftpd[3114]: FTP session closed
Dec  1 23:59:05 ftp1 ftpd[9999]: USER test1
Dec  1 23:59:05 ftp1 ftpd[ 123]: PASS password

现在,您只需遍历文件一次。 由于 PID 列表很小(最大 PID 通常为数万,但即使是一百万也不是那么大),因此存储匹配的每一行不太可能占用大量内存,因此可以存储所有匹配的行。 如果输出顺序无关紧要,您可以避免存储行并像grep一样打印它们。

while(my $line = <DATA>) {
    # Look for a PID
    if(my($pid) = $line =~ m{ [ s*(d+) ]: }x) {
        print $line if $pids{$pid};
    }
}

有关 PID 匹配的说明。 在您的原始示例中,您只是查看 pid 是否在行中的任何位置,$line =~ /$mPID/ . 这是一个问题。 PID 123 将匹配ftpd[1234] . PID 59 将在 23:59:04 上匹配。

通过查找整数,然后查看它们是否在列表中,这使我们免于前者。 ftpd[1234]与 PID 123 不匹配。 但这并不能避免我们意外匹配评论中的日期或其他数字。 根据您给出的样本线,我使用更严格的$line =~ m{ [ s*(d+) ]: }x在正确的位置查找 PID。

您必须查看数据以确定是否可以侥幸逃脱。 如果没有,您至少可以将行中的所有数字与 my @pids = $line =~ m{ (d+) }gx ) 匹配。

你应该从数组中构建一个正则表达式,像这样

my $pids = join '|', @pids;
$pids = qr/$pids/;

然后,您只需对输入文件的每一行进行一次比较。

open my $out_fh, '>', $output or die qq{Couldn't open "$output" for writing: $!n};
while (my $line = <$in_fh>) {
  print $out_fh, $line if $line =~ $pids;
}
close $out_fh;

另请注意,您应该使用具有有意义名称的词法文件句柄,以及 open 的三参数形式。

如果您需要按PID值的顺序对输出进行排序,那么还有更多的工作要做,但这是很有可能的。


更新

如果您需要将每个PID的输出分成几组,则必须在打印之前将输出存储在哈希中,如下所示

my $pids = join '|', @pids;
$pids = qr/($pids)/;
my %output;
while (my $line = <$in_fh>) {
  push @{ $output{$1} }, $line if $line =~ $pids;
}
open my $out_fh, '>', $output or die qq{Couldn't open "$output" for writing: $!n};
for my $pid (@pids) {
  next unless my $lines = $output{$pid};
  print $out_fh $_ for @$lines;
  print $out_fh "###############n";
}
close $out_fh;

请注意,这些解决方案都没有经过 beyind 编译测试,因为创建一组测试数据需要大量的工作。


更新 2

此程序使用更新后问题中的新数据。

use strict;
use warnings;
my $outfile = 'result.txt';
my @pids = qw/ 4682 4690 4692 4693 4696 5320 /;
my $pids = join '|', @pids;
$pids = qr/b($pids)b/;
open my $in_fh, 'logfile.txt' or die $!;
my %output;
while (my $line = <$in_fh>) {
  push @{ $output{$1} }, $line if $line =~ $pids;
}

open my $out_fh, '>', $outfile or die qq{Couldn't open "$outfile" for writing: $!n};
for my $pid (@pids) {
  next unless my $lines = $output{$pid};
  print $out_fh $_ for @$lines;
  print $out_fh "###############n";
}
close $out_fh;

输出

Dec  1 23:59:05 ftp1 ftpd[4682]: USER test1
Dec  1 23:59:05 ftp1 ftpd[4682]: PASS password
###############

我对你的问题的理解是你有:

  • FTP 日志
  • 一个 IP 地址

并且您希望跟踪从该 IP 启动的一个或多个会话。您可以像这样在日志文件中单次传递中执行此操作(我根据您之前提出的另一个问题构建了一些示例数据):

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Regexp::Common qw(net);
my $ip = '192.0.2.0';
my (%pid, %session);
while (<DATA>) {
    chomp;
    if (/ftpd[(d+)]:s+(?:USER|PASS)/) {
        push @{ $session{$1} }, $_;
    }
    elsif (/ftpd[(d+)]:s+FTP LOGIN FROM ($RE{net}{IPv4})/) {
        if ($2 eq $ip) {
            $pid{$1} = 1;
            push @{ $session{$1} }, $_;
        }
        else {
            delete $session{$1};
        }
    }
    elsif (/ftpd[(d+)]:/) {
        push @{ $session{$1} }, $_ if exists $pid{$1};
    }
}
print Dumper %session;
__DATA__
Dec  1 23:59:03 sslmftp1 ftpd[4152]: USER xxxxxx
Dec  1 23:59:03 sslmftp1 ftpd[4152]: PASS password
Dec  1 23:59:03 sslmftp1 ftpd[4152]: FTP LOGIN FROM 192.0.2.0 [192.0.2.0], xxxxxx
Dec  1 23:59:03 sslmftp1 ftpd[4152]: PWD
Dec  1 23:59:03 sslmftp1 ftpd[4152]: CWD /test/data/872507/
Dec  1 23:59:03 sslmftp1 ftpd[4152]: TYPE Image`
Dec  1 23:59:03 sslmftp1 ftpd[4152]: PASV
Dec  1 23:59:04 sslmftp1 ftpd[4152]: NLST
Dec  1 23:59:04 sslmftp1 ftpd[4152]: FTP session closed
Dec  1 23:59:05 sslmftp1 ftpd[4683]: USER xxxxxx
Dec  1 23:59:05 sslmftp1 ftpd[4683]: PASS password
Dec  1 23:59:05 sslmftp1 ftpd[4683]: FTP LOGIN FROM 192.0.2.1 [192.0.2.1], xxxxxx
Dec  1 23:59:05 sslmftp1 ftpd[4683]: PWD
Dec  1 23:59:05 sslmftp1 ftpd[4683]: CWD /test/data/944837/
Dec  1 23:59:05 sslmftp1 ftpd[4683]: TYPE Image
Dec  1 23:59:06 sslmftp1 ftpd[4925]: USER xxxxxx
Dec  1 23:59:06 sslmftp1 ftpd[4925]: PASS password
Dec  1 23:59:06 sslmftp1 ftpd[4925]: FTP LOGIN FROM 192.0.2.0 [192.0.2.0], xxxxxx
Dec  1 23:59:07 sslmftp1 ftpd[4925]: PWD
Dec  1 23:59:08 sslmftp1 ftpd[4925]: CWD /test/data/944837/
Dec  1 23:59:09 sslmftp1 ftpd[4925]: TYPE Image

输出:

$VAR1 = {
          '4152' => [
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: USER xxxxxx  ',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: PASS password  ',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: FTP LOGIN FROM 192.0.2.0 [192.0.2.0], xxxxxx  ',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: PWD  ',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: CWD /test/data/872507/  ',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: TYPE Image`',
                      'Dec  1 23:59:03 sslmftp1 ftpd[4152]: PASV',
                      'Dec  1 23:59:04 sslmftp1 ftpd[4152]: NLST',
                      'Dec  1 23:59:04 sslmftp1 ftpd[4152]: FTP session closed'
                    ],
          '4925' => [
                      'Dec  1 23:59:06 sslmftp1 ftpd[4925]: USER xxxxxx ',
                      'Dec  1 23:59:06 sslmftp1 ftpd[4925]: PASS password',
                      'Dec  1 23:59:06 sslmftp1 ftpd[4925]: FTP LOGIN FROM 192.0.2.0 [192.0.2.0], xxxxxx ',
                      'Dec  1 23:59:07 sslmftp1 ftpd[4925]: PWD',
                      'Dec  1 23:59:08 sslmftp1 ftpd[4925]: CWD /test/data/944837/',
                      'Dec  1 23:59:09 sslmftp1 ftpd[4925]: TYPE Image'
                    ]
        };

现在,您拥有哈希中由$ip启动的每个会话的行,并将会话 PID 作为键。我只是用Data::Dumper打印了它,但您可以随心所欲地操作哈希。

读取文件比循环遍历数组慢。如果输入不是太大,则应将其加载到数组中并遍历该数组:

@input = <INPUT>;
foreach my $mPID(@pids){
    foreach my $line (@input) {
        ...

如果输入太大,那么也许你可以颠倒循环的顺序,这样你仍然只读取文件一次:

while(my $line = <INPUT>){
    foreach my $mPID(@pids){
        if ($line =~ /$mPID/){
            push @{$flow{$mPid}}, $line;
        }
    }
}
open (OUTPUT, '>'.$output) or die "Couldn't read $output.n";
foreach my $mPid (@pids) {
    if (@{$flow{$mPid}}) {
        print OUTPUT @{$flow{$mPid}}, "################n";
    }
}
close (OUTPUT);

相关内容

  • 没有找到相关文章

最新更新