需要帮助在 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 备注将排序中的&lt;=&gt; 更改为cmp

my @sorted = map$_->[0]
             sort $a->[1] cmp $b->[1] 
             map [$_, inet_aton($data$_ip)] keys %data;

【讨论】:

感谢很多帮助,稍加调整我就成功了! my @sorted = map$_-&gt;[0] sort $a-&gt;[1] cmp $b-&gt;[1] map [$_, inet_aton($data$_'ip')] keys %data; 是的,您做出了正确的更改。回顾我的示例文件,我发现我使用了cmp 而不是&lt;=&gt;

以上是关于需要帮助在 perl 中对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Bash 中对数组进行排序

如何在 Perl 6 中对散列进行排序

使用 Perl 中对列表进行排序的索引对另一个列表进行排序和索引

如何在 Java 中对对象数组进行排序?

如何在 ColdFusion 中对结构数组进行排序

在Java中对数组进行排序