需要帮助在 perl 中对数组进行排序
Posted
技术标签:
【中文标题】需要帮助在 perl 中对数组进行排序【英文标题】:Need help sorting array in perl 【发布时间】:2021-11-08 04:07:02 【问题描述】:使用 s perl 脚本解析 isc dhcp 日志,并将其合并到 html 表中。 到目前为止一切都很好,可以正常工作。在脚本中是一个按 IP 地址对结果进行排序的选项。结果不是按最后一个 ip 八位字节排序的,所以它看起来像这样:
192.168.250.149 192.168.250.2 192.168.250.228等等..
我在 perl 方面的技能非常有限,所以我需要帮助来实现这一点。 这是代码部分:
my @sorted = sort ($data$a'ip') cmp ($data$b'ip') %data;
提前谢谢
这是完整的脚本(感谢 Marcin Gosiewski)
use Socket;
use strict;
use warnings;
no warnings 'uninitialized';
# adjust this to match your files location: both log file and leases
# database. We use 2 last log files from logrotate, but you can add as many as you want
my @logfilenames = ( "/var/log/dhcpd.log");
# Alternately, on systems without explicit log (e.g. with systemd journals), use empty array of files:
### my @logfilenames = ( ); # if empty, use output from logprog below
my @logprog = qw ( sudo journalctl --no-pager -lu dhcpd );
# Delegate rights for logprog as root, e.g.
# echo 'www-data ALL=(root) NOPASSWD:/usr/bin/journalctl --no-pager -lu dhcpd' > /etc/sudoers.d/www-journalctl
my $leasedbname = "/var/lib/dhcp/dhcpd.leases";
my %data = ();
# optional, can be modified to produce local time
use Time::Local;
use POSIX 'strftime';
my $now = time();
# local variables, lease information stored here
my $ip="";
my $status="";
my $interface="";
my $sdate=""; # beginning of lease
my $stime="";
my $edate=""; # end of lease
my $etime="";
my $adate=""; # last update (ACK) sent to requesting server
my $atime="";
my $mac="";
my $hostname="";
my $dnsname=""; # reverse dns lookup for host
#######################################################################
# first gather data from logfile for all ACK actions
#######################################################################
# collect all lines from log files into memory...
my @lines = (); my @loglines=();
if (scalar @logfilenames > 0)
foreach my $logfilename (@logfilenames)
open LOGFILE, '<', $logfilename;
chomp(@loglines = <LOGFILE>);
#printf "LINES1: " . scalar @loglines . " in " .$logfilename . "\n";
push(@lines, @loglines);
close(LOGFILE);
else
open LOGPROG, '-|', join (' ', @logprog) or die "Could not pipe from logprog";
chomp(@loglines = <LOGPROG>);
#printf "LINES1: " . scalar @loglines . " in " .$logfilename . "\n";
push(@lines, @loglines);
close(LOGPROG);
@loglines=();
#printf "TOTAL LINES: " . scalar @lines . "\n";
foreach my $line (@lines)
if ( $line !~ m/dhcpd[^:]*: DHCPACK/) next;
#printf "LINE: $line\n";
###############################
# Modify the following line to make regexp capture 6 groups from log line:
# 1 - date
# 2 - time
# 3 - ip
# 4 - mac
# 5 - hostname if available
# 6 - interface
#$line =~ m/(^.10)T(.8).+,\ dhcpd: DHCPACK on (\d1,3\.\d1,3\.\d1,3\.\d1,3) to ((?:[0-9a-f]2[:-])5[0-9a-f]2.*) via (.+)/;
#$line =~ m/(^.10)T(.8).+,\ dhcpd: DHCPACK on (\d1,3\.\d1,3\.\d1,3\.\d1,3) to ((?:[0-9a-f]2[:-])5[0-9a-f]2) (.*)via (.+)/;
$line =~ m/^(.6) (.8)\ .+,?\ dhcpd[^:]*: DHCPACK on (\d1,3\.\d1,3\.\d1,3\.\d1,3) to ((?:[0-9a-f]2[:-])5[0-9a-f]2) (.*)via (.+)/;
# process the input
$adate="$1";
$atime="$2";
$ip="$3";
$mac="$4";
$hostname="$5";
$interface="$6";
#add some 'known' facts:
$status="ACK";
$sdate=""; #"FOREVER";
$stime="";
$edate="";
$etime="";
#create/update record for this mac_addr
#you can add extra check here if the IP address is not duplicated within
#ack history and choose only the newer one.
$data"$mac"->'ip' = "$ip";
$data"$mac"->'status' = "$status";
$data"$mac"->'interface' = "$interface";
$data"$mac"->'adate' = "$adate";
$data"$mac"->'atime' = "$atime";
$data"$mac"->'sdate' = "$sdate";
$data"$mac"->'stime' = "$stime";
$data"$mac"->'edate' = "$edate";
$data"$mac"->'etime' = "$etime";
$data"$mac"->'mac' = "$mac";
if (length($hostname) > 0)
$hostname =~ s/^\ *\(*//;
$hostname =~ s/\)*\ *$//;
$data"$mac"->'hostname' = "$hostname";
#close(LOGFILE);
#######################################################################
# gather data from lease database for dynamic addresses
# update the records (for existing) or add new records
#######################################################################
my $isdata = 0;
my $type = "";
#this information is not present in leases database so we just set
#it to default values
$interface="dhcpd";
$status="ACTIVE";
$adate="-";
$atime="";
open LEASEDB, $leasedbname or die $!;
foreach my $line (<LEASEDB>)
chomp($line);
$isdata = 1 if $line =~ /^lease /;
$isdata = 0 if $line =~ /^/;
if ($isdata)
if ($line =~ /^lease/)
$ip = (split(" ", $line))[1];
elsif ($line =~ /^ starts/)
($sdate, $stime) = (split(" ", $line))[2,3];
$sdate =~ s/\//-/g;
$stime =~ s/;//;
elsif ($line =~ /^ ends/)
($type, $edate, $etime) = (split(" ", $line))[1,2,3];
if($type eq "never;")
$edate="forever";
$etime=" ";
else
$edate =~ s/\//-/g;
$etime =~ s/;//;
elsif ($line =~ /^ hardware ethernet/)
$mac = (split(" ", $line))[2];
$mac =~ s/;//;
elsif ($line =~ /^ client-hostname/)
$hostname = (split(/\"/, $line))[1];
elsif($mac ne "")
#we have parsed the whole record, no more matching entries
#data is collected to variables. now push the record.
#now let's decide if we are updating the record or creating
#new record
# check against lease date, do not add expired leases
# convert lease end time to local time/date and compare with $now
my $y=0; my $m=0; my $d=0; my $H=0; my $M=0; my $S=0;
my $edatetime = $now;
($y, $m, $d) = split("-", $edate);
($H, $M, $S) = split(":", $etime);
$edatetime = timelocal($S,$M,$H,$d,$m-1,$y);
if($edatetime >= $now)
# now check if record exists
if(!defined($data"$mac"->'mac'))
#record does not exist, fill up default data
$data"$mac"->'mac' = "$mac";
$data"$mac"->'interface' = "$interface";
$data"$mac"->'ip' = "$ip";
$data"$mac"->'hostname' = "$hostname";
# record exists, let's check if we should update
$data"$mac"->'status' = "$status";
$data"$mac"->'sdate' = "$sdate";
$data"$mac"->'stime' = "$stime";
$data"$mac"->'edate' = "$edate";
$data"$mac"->'etime' = "$etime";
$data"$mac"->'hostname' = "$hostname";
#we do NOT update ACK time because we do not have it
#do NOT uncomment below
#$data"$mac"->'adate' = "$adate";
#$data"$mac"->'atime' = "$atime";
close(LEASEDB);
#######################################################################
# sort data
#######################################################################
#we sort by IP but you can sort by anything.
#my @sorted = sort ($data$a'ip') cmp ($data$b'ip') %data;
my @sorted = sort ($data$a'ip') cmp ($data$b'ip') %data;
#foreach my $key (@sorted)
# printf $data$key'ip';
# ;
#######################################################################
# Print out everything to the HTML table
#######################################################################
my $hostnamelong="";
printf "Content-type: text/html\n\n";
printf "<html><head><title>DHCP LOG</title></head>\n";
printf "<style> table, th, td border: 1px solid lightgray; border-collapse: collapse; padding: 3px; ";
printf "tr:nth-child(even) background-color: #dddddd; ";
printf "body font-family: 'Courier New', monospace; ";
printf "</style>\n";
printf "<body>\n";
printf "<table border='1' cellpadding='6'>\n";
printf "<tr><th>IP</th><th>Status</th><th>Interface</th><th>Lease time</th><th>ACK time</th><th>Mac</th><th>Host</th></tr>\n";
foreach my $key (@sorted)
if($data$key'mac' eq "") next ;
# BEGIN reverse dns lookup
# can optionally turn off reverse dns lookup (comment out below lines) which speeds up the process
# of table creation and is useless unless you have reverse dns populated for
# your fixed or dynamic leases uncomment single line below instead:
#
# version without reverse dns lookup:
#$hostnamelong = $data$key'hostname';
#
# version with reverse dns lookup:
# BEGIN
$dnsname = gethostbyaddr(inet_aton($data$key'ip'), AF_INET);
if($data$key'hostname' ne "")
$hostnamelong = $data$key'hostname' . " | " . $dnsname;
else
$hostnamelong = $dnsname;
$dnsname = "";
# END
printf "<tr>";
printf "<td>" . $data$key'ip' ."</td>";
printf "<td>" . $data$key'status' ."</td>";
printf "<td>" . $data$key'interface' ."</td>";
printf "<td>" . $data$key'sdate' . " " . $data$key'stime' ." - ";
printf $data$key'edate' . " " . $data$key'etime' ."</td>";
printf "<td>" . $data$key'adate' . " " . $data$key'atime' . "</td>";
printf "<td>" . $data$key'mac' ."</td>";
printf "<td>" . $hostnamelong ."</td>";
printf "</tr>\n";
printf "</table>\n";
printf "</body></html>\n";
【问题讨论】:
%data
看起来像什么?你想保留值还是键(以%
开头的变量在 Perl 中保存关联数据)。
请访问以下page 并提供您问题的示例代码。
CSS file 上面的代码——存储在./css/table.css
。
【参考方案1】:
您可以使用 Socket 的inet_aton
我认为这是获取按ip排序的哈希键的方法:
编辑:根据下面的HugoBoss's
备注将排序中的<=>
更改为cmp
。
my @sorted = map$_->[0]
sort $a->[1] cmp $b->[1]
map [$_, inet_aton($data$_ip)] keys %data;
【讨论】:
感谢很多帮助,稍加调整我就成功了!my @sorted = map$_->[0] sort $a->[1] cmp $b->[1] map [$_, inet_aton($data$_'ip')] keys %data;
是的,您做出了正确的更改。回顾我的示例文件,我发现我使用了cmp
而不是<=>
。以上是关于需要帮助在 perl 中对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章