好的,所以我用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);