Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I strongly advise against parsing the output of <code>last</code>, as its output may differ from implementation to implementation and parsing the login/logout dates is prone to error. Also, it seems that nearly all implementations don't support <code>-F</code> or similar which without you are completely out of luck, as you need the year information. In theory you could check if there is a leap from one month to another one that is more recent on two consecutive lines (e.g. Jan->Dec would indicate a year change), but this heuristic is flawed - you just cannot guess the correct year(s) correctly. For example, take the rare case that nobody logged in for a year.</p> <p>If you absolutely have to/want to parse its output either way, don't do it with just bash/awk/cut/... for the reasons above. To get the session duration you would either have to parse the prettyprinted login/logout dates yourself or the already calculated duration, which is also prettyprinted and probably varies from implementation to implementation (as in, it's not just hours and minutes. How do get days/weeks/years represented in that column?). Doing this with just bash/awk would be a nightmare and even more prone to breakage than my script below - please don't do it.</p> <p>The best and least hacky solution would involve writing a small C program or script that operates on the <code>wtmp</code> data directly (<code>man wtmp</code>), but then you would have to calculate the session durations yourself based on login/logout pairs (you don't get this for free; login is one record, logout is a second one). See <a href="http://git.busybox.net/busybox/tree/miscutils/last_fancy.c" rel="nofollow">busybox' <code>last</code> implementation</a> for a reference on how it reads its stuff. This is the way to go if you want to do it the right way.</p> <p>That being said, I came up with the quick'n'dirty (perl) solution below. It doesn't run the <code>last</code> command, you have to feed it proper input yourself, otherwise it will explode. If your <code>last</code> output looks different than mine, doesn't support <code>-F</code> or <code>Date::Parse</code> cannot parse the format your <code>last</code> command prints, it will also explode. There is lots of room for improvement, but this should get you started.</p> <h2>Notes</h2> <ul> <li><code>-F</code> is required for <code>last</code> to print full dates (we need this to get the year, otherwise we cannot determine proper timestamps from its output)</li> <li><code>-i</code> tells last to output IP addresses, which just makes its output easier to parse</li> <li>it does not parse the session duration column but rather both login/logout dates, converts them to epoch time and calculates the diff to get the session duration. There is no other magic involved in parsing the dates other than using <code>Date::Parse</code>, which means that it has to exclude all sessions that don't have a proper login/logout date (i.e., they are still logged in or their session got terminated due to a reboot, crash, etc.), <strong>so these sessions won't be part of the calculated output!</strong></li> <li>it defaults to 7 days, but you can change this on the command line with the <code>-d</code> switch</li> </ul> <h2>Code</h2> <pre><code>#!/usr/bin/perl use strict; use warnings; use Date::Parse; use Getopt::Std; our $opt_d; getopt('d'); my $days = $opt_d || 7; my $since = time() - (60 * 60 * 24 * $days); my %data; while (&lt;&gt;) { chomp; next if /ssh|reboot|down|crash|still logged in/; # last -Fi gives this on my box # username line ip Mon Apr 1 18:17:49 2013 - Tue Apr 2 01:00:45 2013 (06:42) my ($user, undef, undef, $date_from, $date_to) = /^(\S+)\s+(\S+)\s+([0-9.]+)\s+([[:alnum:]:\s]+)\s+-\s+([[:alnum:]:\s]+[^\s])\s+\(.+\)/; my $time_from = str2time($date_from); last if $time_from &lt; $since; my $time_to = str2time($date_to); $data{$user}{"count"}++; $data{$user}{"duration"} += $time_to - $time_from; # print "$user|$line|$ip|$date_from|$date_to\n"; } print "login history for the last $days day(s):\n\n"; if (keys %data &gt; 0) { foreach my $user (keys %data) { my $duration = $data{$user}{"duration"}; printf "%s was logged in %d time(s) for a total of %d day(s), %d hour(s) and %d minute(s)\n", $user, $data{$user}{"count"}, ($duration / (24 * 60 * 60)), ($duration / (60 * 60 )) % 24, ($duration / 60 ) % 60, } } else { print "no logins during the specified time period\n"; } </code></pre> <h2>Example usage</h2> <pre><code>$ last -Fi | ./last_parse.pl -d 700 login history for the last 700 day(s): root was logged in 25 time(s) for a total of 36 day(s), 12 hour(s) and 35 minute(s) foobar was logged in 362 time(s) for a total of 146 day(s), 17 hour(s) and 17 minute(s) quux was logged in 3 time(s) for a total of 0 day(s), 0 hour(s) and 4 minute(s) $ </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload