需要帮助在 perl 中排序数组
Need help sorting array in perl
使用 perl 脚本解析 isc dhcp 日志,并将其合并到 html table。
到目前为止一切顺利,可以正常工作。脚本中有一个选项可以按 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 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}.*) via (.+)/;
#$line =~ m/(^.{10})T(.{8}).+,\ dhcpd: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) (.*)via (.+)/;
$line =~ m/^(.{6}) (.{8})\ .+,?\ dhcpd[^:]*: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) (.*)via (.+)/;
# process the input
$adate="";
$atime="";
$ip="";
$mac="";
$hostname="";
$interface="";
#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";
你可以使用Socket的inet_aton
我认为这是获取按 ip 排序的哈希键的方法:
编辑:根据下面的 HugoBoss's
评论,将排序中的 <=>
更改为 cmp
。
my @sorted = map{$_->[0]}
sort{ $a->[1] cmp $b->[1]}
map {[$_, inet_aton($data{$_}{ip})]} keys %data;
使用 perl 脚本解析 isc dhcp 日志,并将其合并到 html table。 到目前为止一切顺利,可以正常工作。脚本中有一个选项可以按 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 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}.*) via (.+)/;
#$line =~ m/(^.{10})T(.{8}).+,\ dhcpd: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) (.*)via (.+)/;
$line =~ m/^(.{6}) (.{8})\ .+,?\ dhcpd[^:]*: DHCPACK on (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) to ((?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}) (.*)via (.+)/;
# process the input
$adate="";
$atime="";
$ip="";
$mac="";
$hostname="";
$interface="";
#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";
你可以使用Socket的inet_aton
我认为这是获取按 ip 排序的哈希键的方法:
编辑:根据下面的 HugoBoss's
评论,将排序中的 <=>
更改为 cmp
。
my @sorted = map{$_->[0]}
sort{ $a->[1] cmp $b->[1]}
map {[$_, inet_aton($data{$_}{ip})]} keys %data;