http://paperlined.org/dev/perl/parse/parse_wtmp.pl

#!/usr/bin/perl

    use strict;
    use warnings;

    #use Data::Dumper();   use Perl::Tidy;
    #sub Dumper {perltidy source=>\(Data::Dumper::Dumper@_),destination=>\(my$t);$t}

    use Data::Dumper;

    $Data::Dumper::Quotekeys = 0;
    $Data::Dumper::Sortkeys  = 1;





print Dumper [ parse_wtmp() ];


sub parse_wtmp {
    # 'c2ph' was used to generate this,
    # with help from dump_size_offset() to confirm/fix alignment:
    #       http://paperlined.org/dev/perl/modules/related_modules/more_complex_than_pack.html
    my ($fnames, $ftypes) = unzip(qw_comments(<<'EOF'));
        type    l           # short int                 type of login (0 through 9); see wtmp(5)
        pid     i           # pid_t                     PID of login process
        line    Z32         # char[UT_LINESIZE]         device name of TTY
        id      Z4          # char[4]                   terminal name suffix, or inittab ID
        user    Z32         # char[UT_NAMESIZE]         username
        host    A256        # char[UT_HOSTSIZE]         hostname (if remote login)
        exit    l           # struct exit_status        exit status of a process when ut_type==DEAD_PROCESS
        session l           # long int                  session ID, used for windowing
        tv_sec  i           # struct timeval.tv_sec     time this entry was made
        tv_usec i           # struct timeval.tv_usec
        addr_v6 a16         # int32_t[4]                internet address of remote host; IPv4 address uses just ut_addr_v6[0]
        unused  Z20         # char[20]                  reserved for future use
EOF
    $ftypes = join(" ", @$ftypes);
    open my $fin, '<', '/var/log/wtmp'  or die $!;
    local $/ = \(length pack $ftypes);       # read fixed-length records
    my @entries;
    while (<$fin>) {
        my %ent;
        @ent{@$fnames} = unpack $ftypes, $_;
        $ent{tv_human} = ~~localtime $ent{tv_sec};
        if (!grep {$_} unpack 'x4L3', $ent{addr_v6}) {     # if the last 12 bytes are all-zero, then it's an IPv4 address
            $ent{addr_human} = join '.', unpack 'C4', $ent{addr_v6};
        } else {
            $ent{addr_human} = join ':', unpack 'H4H4H4H4H4H4H4H4', $ent{addr_v6};
        }
        push @entries, \%ent;
    }
    return @entries;
}



# split [1,'a',2,'b',3,'c'] into [[1,2,3],['a','b','c']]
sub unzip {my($i,$j)=(0,0); [grep{++$i%2}@_],[grep{$j++%2}@_]}

# Like qw[...], but it allows use of comments (hash symbol).
sub qw_comments {local$_=shift;s/\s+#.*//gm;split}

Generated by GNU enscript 1.6.4.