Berechnen Sie die Anmeldedauer eines Benutzers in Minuten aus einer Datei

Berechnen Sie die Anmeldedauer eines Benutzers in Minuten aus einer Datei

Ich habe eine Datei auf einem Linux-System, die wie folgt aussieht:

May 6 19:12:03 sys-login: user1 172.16.2.102 Login /data/netlogon 13473
May 6 19:15:26 sys-login: user2 172.16.2.107 Login /data/netlogon 14195
May 6 19:28:37 sys-logout: user1 172.16.2.102 Logout /data/netlogon 13473
May 6 19:33:28 sys-logout: user2 172.16.2.107 Logout /data/netlogon 14195
May 8 07:58:50 sys-login: user3 172.16.6.128 Login /data/netlogon 13272
May 8 07:58:50 sys-logout: user3 172.16.6.128 Logout /data/netlogon 13272

Und ich versuche, die Zeit in Minuten zu berechnen, die jeder Benutzer zwischen dem Anmelden und Abmelden verbracht hat. Es wird nur eine Anmeldung/Abmeldung pro Benutzer geben, und ich möchte einen Bericht für alle Benutzer gleichzeitig erstellen.

Was ich versucht habe:

Ich habe zunächst versucht, die Benutzer zu extrahieren:

users=$(awk -v RS=" " '/login/{getline;print $0}' data)

das gibt die Benutzer (angemeldet) zurück, und dann versuche ich, die Zeit zu ermitteln, zu der sie sich angemeldet haben, aber ich stecke derzeit fest. Für jede Hilfe wäre ich dankbar!

Bearbeiten: Ich kann Benutzer und Daten wie folgt abrufen:

users=$(grep -o 'user[0-9]' data)
dates=$(grep -o '[0-2][0-9]:[0-5][0-9]:[0-5][0-9]' data)

Wenn ich eine vollständige Lösung finde, werde ich sie hier teilen.

Antwort1

Obwohl diese Site „kein Skript-Schreibservice“ ist ;), ist dies eine nette kleine Übung, daher schlage ich das folgende awkProgramm vor. Sie können es in einer Datei speichern calc_logtime.awk.

#!/usr/bin/awk -f

/sys-log[^:]+:.*Log/ {
    user=$5
    cmd=sprintf("date -d \"%s %d %s\" \"+%%s\"",$1,$2,$3)
    cmd|getline tst
    close(cmd)

    if ($7=="Login") {
        login[user]=tst
    }
    else if ($7=="Logout") {
        logtime[user]+=(tst-login[user])
        login[user]=0
    }
}

END {
    for (u in logtime) {
    minutes=logtime[u]/60
    printf("%s\t%.1f min\n",u,minutes)
    }
}

Dies setzt voraus, dass der GNU- dateBefehl verwendet wird (Teil der Standard-Tool-Suite auf GNU/Linux-Systemen) und dass das Zeitformat in Ihrer Protokolldatei wie angegeben ist. Beachten Sie auch, dass dies nicht viele Sicherheitsprüfungen enthält, aber Sie sollten eine Vorstellung davon bekommen, wie Sie es an Ihre Bedürfnisse anpassen können.

  • Es wird nach Zeilen gesucht, die enthaltenbeidedie Zeichenfolge sys-logam Anfang und Logam Ende, um die Selektivität zu erhöhen, falls es noch anderen Inhalt geben könnte. Wie gesagt, dies ist ein sehr rudimentärer Test, aber Sie können sich auch hier ein Bild davon machen, wie man ihn spezifischer gestalten kann.
  • Der Benutzer wird als 5. durch Leerzeichen getrenntes Feld der Zeile extrahiert.
  • Die Aktion wird als 7. durch Leerzeichen getrenntes Feld der Zeile extrahiert.
  • Der Zeitstempel der Aktion wird in „Sekunden seit der Epoche“ umgewandelt, indem ein dateAufruf über generiert sprintfund die Aufgabe an die Shell delegiert wird.
  • Wenn die Aktion ist Login, wird der Zeitstempel in einem Array gespeichert login, mit dem Benutzernamen als „Array-Index“.
  • Wenn die Aktion lautet Logout, wird die Dauer berechnet und zu einem Array hinzugefügt, logtimedas die bisherige Gesamtprotokollzeit für alle Benutzer enthält.
  • Am Ende der Datei wird ein Bericht generiert, indem alle „Array-Indizes“ durchlaufen logtimeund die Protokollzeiten durch einfache Division von Sekunden in Minuten umgerechnet werden.

Sie erreichen es über

awk -f calc_logtime.awk logfile.dat

Antwort2

Mit GNU awk für Zeitfunktionen und gensub() und Arrays von Arrays:

$ cat tst.awk
BEGIN {
    dateFmt = strftime("%Y") " %02d %02d %s"
    months  = "JanFebMarAprMayJunJulAugSepOctNovDec"
}
{
    date = sprintf(dateFmt, (index(months,$1)+2)/3, $2, gensub(/:/," ","g",$3))
    userSecs[$5][$7] = mktime(date)
}
$7 == "Logout" {
    printf "%s %0.2f\n", $5, (userSecs[$5]["Logout"] - userSecs[$5]["Login"]) / 60
    delete userSecs[$5]
}

$ awk -f tst.awk file
user1 16.57
user2 18.03
user3 0.00

Dies läuft um Größenordnungen schneller als der Aufruf von Unix dateaus awk, da letzteres hierfür jedes Mal eine Subshell starten muss.

Wenn Sie beim Ausführen des Skripts auch einen Bericht über die Benutzer erhalten möchten, die sich angemeldet, aber nicht abgemeldet haben, beispielsweise user4in dieser geänderten Eingabedatei:

$ cat file
May 6 19:12:03 sys-login: user1 172.16.2.102 Login /data/netlogon 13473
May 6 19:15:26 sys-login: user2 172.16.2.107 Login /data/netlogon 14195
May 6 19:28:37 sys-logout: user1 172.16.2.102 Logout /data/netlogon 13473
May 6 19:33:28 sys-logout: user2 172.16.2.107 Logout /data/netlogon 14195
May 8 07:58:50 sys-login: user3 172.16.6.128 Login /data/netlogon 13272
May 8 07:58:50 sys-logout: user3 172.16.6.128 Logout /data/netlogon 13272
Jun 15 08:30:26 sys-login: user4 172.16.2.107 Login /data/netlogon 14195

Dann optimieren Sie einfach das Skript:

$ cat tst.awk
BEGIN {
    dateFmt = strftime("%Y") " %02d %02d %s"
    months  = "JanFebMarAprMayJunJulAugSepOctNovDec"
}
{
    date = sprintf(dateFmt, (index(months,$1)+2)/3, $2, gensub(/:/," ","g",$3))
    userSecs[$5][$7] = mktime(date)
}
$7 == "Logout" {
    printf "%s %0.2f %s\n", $5, (userSecs[$5]["Logout"] - userSecs[$5]["Login"]) / 60, "Complete"
    delete userSecs[$5]
}
END {
    now = systime()
    for (user in userSecs) {
        printf "%s %0.2f %s\n", user, (now - userSecs[user]["Login"]) / 60, "Partial"
    }
}

$ awk -f tst.awk file
user1 16.57 Complete
user2 18.03 Complete
user3 0.00 Complete
user4 51.10 Partial

Wenn Sie Fälle finden müssten, in denen sich ein Benutzer erneut angemeldet hat, während er bereits angemeldet war, ohne sich zwischendurch abzumelden, oder Abmeldungen ohne zugehörige Anmeldung anders behandeln oder irgendetwas anderes tun müssten, dann wären das alles ebenfalls nur triviale Optimierungen.

Antwort3

Das folgende perlSkript verwendet dieDatum::ParseModul aus demZeit DatumSammlung, um die Daten und Zeiten aus jedem Datensatz zu analysieren, anstatt sich dabei auf GNU Date zu verlassen. Dies ist wahrscheinlich für Ihre Distribution gepackt (unter Debian, apt install libtimedate-perl), andernfalls installieren Sie es mit cpan.

Das Skript funktioniert, indem es das letzte Feld jeder Eingabezeile (das eine Sitzungs-ID zu sein scheint) als Schlüssel der obersten Ebene für eine Hash-of-Hashes (HoH)-Datenstruktur namens verwendet %sessions. Jedes Element von %sessions ist ein anonymer Hash, der die Schlüssel user, login, und enthält logout.

Sobald die gesamte Datei eingelesen und analysiert wurde, werden die kumulierten Gesamtsummen für jeden Benutzer berechnet (und in einem anderen assoziativen Array gespeichert %users) und dann gedruckt. Die Ausgabe wird nach Benutzernamen sortiert.

#!/usr/bin/perl -l

use strict;
use Date::Parse;

my %sessions;
my %users;

# read the input file, parse dates, store login and logout times into session hash
while (<>) {
  next unless (m/\ssys-log(?:in|out):\s/);

  my ($M, $D, $T, $type, $user, $ip, undef, undef, $s) = split;
  $type =~ s/^sys-|://g;

  $sessions{$s}->{user} = $user;
  $sessions{$s}->{$type} = str2time(join(" ", $M, $D, $T));
  # $session{$s}->{IP} = $ip; # not used
};

# add up session totals for each user
foreach my $s (keys %sessions) {
  # ignore sessions without both a login and logout time, it's
  # impossible to calculate session length.
  next unless ( defined($sessions{$s}->{login}) &&
                defined($sessions{$s}->{logout}) );

  $users{$sessions{$s}->{user}} += $sessions{$s}->{logout} - $sessions{$s}->{login};
};

# print them
foreach my $u (sort keys %users) {
   printf "%s has logged in for %s minutes\n", $u, int($users{$u}/60); 
};

Speichern Sie es beispielsweise unter login-times.plund machen Sie es mit ausführbar chmod +x login-times.pl. Führen Sie es wie folgt aus:

$ ./login-times.pl data
user1 has logged in for 16 minutes
user2 has logged in for 18 minutes
user3 has logged in for 0 minutes

Zu Ihrer Information: Die Daten im %sessionsHoH sehen folgendermaßen aus:

%sessions = {
  13272 => { login => 1620424730, logout => 1620424730, user => "user3" },
  13473 => { login => 1620292323, logout => 1620293317, user => "user1" },
  14195 => { login => 1620292526, logout => 1620293608, user => "user2" },
}

Es ist durchaus möglich, dass eine Sitzung weder einen Anmelde- noch einen Abmeldezeitstempel hat. Es wäre einfach, eine Meldung an STDERR auszugeben, wenn einer von beiden fehlt. Oder Sie können solche Anomalien nach Belieben behandeln. Das obige Skript ignoriert sie einfach.

Der Vollständigkeit halber %userssehen die Daten am Ende folgendermaßen aus:

%users = { user1 => 994, user2 => 1082, user3 => 0 }

Übrigens wurden diese Datenstrukturen mit demDatenmüllModul, das zum Debuggen usw. sehr nützlich ist. Der Debian-Paketname ist libdata-dump-perl, andere Distributionen haben es wahrscheinlich. Andernfalls installieren Sie es mit cpan.

Um diese auszudrucken, habe ich am Ende des Skripts Folgendes hinzugefügt:

use Data::Dump qw(dump);
print "%sessions = ", dump(\%sessions);
print "%users = ", dump(\%users)

Schließlich wird die IP-Adresse mit der Funktion im Skript erfasst, splitaber nicht verwendet. Diese könnte leicht zum Sitzungs-Hash hinzugefügt und verwendet werden, um eine einzeilige Zusammenfassung jedes Login- und Logout-Paares auszudrucken. DieDatumsformatTime::DateZum Formatieren der Daten könnte ein Modul aus derselben Sammlung verwendet werden.

Zum Beispiel:

  1. use Date::Format;nach der use Date::Parse;Zeile hinzufügen

  2. Entfernen Sie die Kommentierung $session{$s}->{IP} = $ip;aus der while(<>)Schleife.

  3. Verwenden Sie zum Ausdrucken der Daten beispielsweise Folgendes:

my $tfmt = "%Y-%m-%d %H:%M:%S";

printf "%s\t%-20s\t%-20s\t%7s\t%s\n", "USER", "LOGIN", "LOGOUT", "MINUTES", "IP";

# sort the session keys by their 'user' fields.
foreach my $s (sort { $sessions{$a}->{user} cmp $sessions{$b}->{user} } keys %sessions) {
  my $in  = $sessions{$s}->{login};
  my $out = $sessions{$s}->{logout};
  next unless ($in && $out);

  my $user = $sessions{$s}->{user};
  my $ip   = $sessions{$s}->{IP};

  my $minutes = int(($out-$in)/60);
  $in  = time2str($tfmt,$in); 
  $out = time2str($tfmt,$out);

  printf "%s\t%-20s\t%-20s\t%7i\t%s\n", $user, $in, $out, $minutes, $ip;
};

Die Ausgabe sähe etwa so aus:

USER    LOGIN                   LOGOUT                  MINUTES IP
user1   2021-05-06 19:12:03     2021-05-06 19:28:37          16 172.16.2.102
user2   2021-05-06 19:15:26     2021-05-06 19:33:28          18 172.16.2.107
user3   2021-05-08 07:58:50     2021-05-08 07:58:50           0 172.16.6.128

Antwort4

Das klingt nach einem Job für dateutils. Fischen Sie die relevanten Teile heraus mit awk:

awk -v OFS='\t' '
$4 == "sys-login:"  { login[$5]  = $1" "$2" "$3 }
$4 == "sys-logout:" { logout[$5] = $1" "$2" "$3 }
END {
  for (user in login)
    print user, login[user], logout[user]
}' infile

Ausgabe:

user1   May 6 19:12:03  May 6 19:28:37
user2   May 6 19:15:26  May 6 19:33:28
user3   May 8 07:58:50  May 8 07:58:50

Und leiten Sie es an eine While-Schleife weiter:

while IFS=$'\t' read username starttime endtime; do
  printf "%s\t%s\n" $username \
    $(dateutils.ddiff -i "%b %d %H:%M:%S" -f "%S" "$starttime" "$endtime")
done

Ausgabe:

user1   994
user2   1082
user3   0

Hinweis: Sie können den Schalter ddiffdes Befehls ändern -f, um ein anderes Zeitformat auszuwählen. Hier verwenden wir die verstrichenen Sekunden.

verwandte Informationen