好的,所以我在bash中编写了一个脚本,通过搜索用户名或IP地址来显示ftp连接的整个流程。我让它将数据读入数组,搜索条件,然后将该进程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
谢谢!
答案 0 :(得分:3)
任何时候你在循环中有一个循环,这可能是一个性能问题。我们假设你有1000个pid和100万行日志文件。对每个pid循环遍历文件中的每一行是1000 * 1百万,这是1 BILLION DOLLARS!!! Err ...迭代。
现在你要检查每一行是否有每个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: $pid\n";
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 )
匹配。
答案 1 :(得分:2)
你应该从数组中构建一个正则表达式,比如
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
###############
答案 2 :(得分:2)
我对你的问题的理解是你有:
并且您希望跟踪从该IP启动的会话。您可以像这样在我的日志文件中一次性完成此操作(我根据您之前询问的another question构建了一些示例数据):
#!/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
打印它,但您可以随意操作散列。
答案 3 :(得分:1)
读取文件比循环遍历数组要慢。如果输入不是太大,则应将其加载到数组中并循环遍历该数组:
@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);