Рассчитать длительность входа пользователя в систему в минутах из файла

Рассчитать длительность входа пользователя в систему в минутах из файла

У меня в системе Linux есть файл, который выглядит следующим образом:

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

И я пытаюсь подсчитать время, которое каждый пользователь провел между входом в систему и выходом из нее в минутах. Будет только один вход/выход из системы на пользователя, и я хочу сгенерировать отчет для всех пользователей сразу.

Что я пробовал:

Я попытался сначала извлечь пользователей:

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

который возвращает пользователей (вошедших в систему), а затем я пытаюсь извлечь время, когда они вошли в систему, но в данный момент я застрял. Любая помощь будет оценена по достоинству!

Редактировать: Я могу получить пользователей и даты, выполнив следующие действия:

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

Если я найду полное решение, я поделюсь им здесь.

решение1

Хотя этот сайт "не является сервисом по написанию скриптов" ;), это приятное маленькое упражнение, поэтому я предложу следующую awkпрограмму. Вы можете сохранить ее в файл 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)
    }
}

Это зависит от использования команды GNU date(часть стандартного набора инструментов в системах GNU/Linux) и от того, что формат времени в вашем файле журнала соответствует указанному. Также обратите внимание, что это не содержит много проверок безопасности, но вы должны получить представление о том, как изменить его в соответствии со своими потребностями.

  • Он будет искать строки, содержащиеобастрока sys-logоколо начала и Logоколо конца, чтобы увеличить селективность на случай, если может быть другой контент. Как уже говорилось, это очень элементарный тест, но, опять же, вы можете получить представление о том, как сделать его более конкретным.
  • Пользователь будет извлечен как пятое поле строки, разделенное пробелом.
  • Действие будет извлечено как 7-е поле строки, разделенное пробелом.
  • Временная метка действия будет преобразована в «секунды с начала эпохи» путем генерации dateвызова sprintfи делегирования задачи оболочке.
  • Если действие — Login, то временная метка сохраняется в массиве login, а имя пользователя — в качестве «индекса массива».
  • Если действие равно Logout, длительность будет рассчитана и добавлена ​​в массив, logtimeсодержащий общее время входа для всех пользователей на данный момент.
  • В конце файла будет сформирован отчет путем перебора всех «индексов массива» logtimeи преобразования времени регистрации из секунд в минуты простым делением.

Вы можете позвонить через

awk -f calc_logtime.awk logfile.dat

решение2

С GNU awk для функций времени и gensub() и массивов массивов:

$ 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

Это будет работать на несколько порядков быстрее, чем вызов Unix dateиз awk, поскольку последнему для этого каждый раз приходится создавать подоболочку.

Если вы также хотите получить отчет о пользователях, которые вошли в систему, но не вышли из нее при запуске скрипта, например, user4в этом измененном входном файле:

$ 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

Затем просто подправьте скрипт:

$ 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

Если вам нужно найти случаи, когда пользователь снова вошел в систему, уже выполнив вход, не выходя из нее, или по-другому обрабатывать выходы из системы без соответствующего входа или сделать что-то еще, то это все тоже будет просто тривиальными настройками.

решение3

Следующий perlскрипт используетДата::Анализмодуль изВремяДатаcollection для разбора дат и времени из каждой записи вместо того, чтобы полагаться на GNU date, чтобы сделать это. Это, вероятно, упаковано для вашего дистрибутива (на Debian, apt install libtimedate-perl), в противном случае установите его с помощью cpan.

Скрипт работает, используя последнее поле каждой входной строки (которое, по-видимому, является идентификатором сеанса) в качестве ключа верхнего уровня для структуры данных Hash-of-Hashes (HoH), называемой %sessions. Каждый элемент %sessions представляет собой анонимный хеш, содержащий ключи user, login, и logout.

После того, как весь файл был прочитан и проанализирован, для каждого пользователя вычисляются совокупные итоги (и сохраняются в другом ассоциативном массиве, %users), а затем печатаются. Вывод сортируется по имени пользователя.

#!/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); 
};

Сохраните его как, например, login-times.plи сделайте его исполняемым с помощью chmod +x login-times.pl. Запустите его так:

$ ./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

К вашему сведению, данные в %sessionsHoH выглядят так:

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

Вполне возможно, что сеанс не будет иметь ни временной метки входа, ни временной метки выхода. Было бы легко вывести сообщение в STDERR, если бы что-то из этого отсутствовало. Или обрабатывать такие аномалии любым удобным для вас способом. Скрипт выше просто игнорирует их.

Для полноты картины данные в %usersитоге выглядят следующим образом:

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

Кстати, эти структуры данных были напечатаны с помощьюВывод данныхмодуль, который очень полезен для отладки и т. д. Имя пакета Debian — libdata-dump-perl, в других дистрибутивах он, вероятно, есть. В противном случае установите его с помощью cpan.

Чтобы их распечатать, я добавил в конец скрипта следующее:

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

Наконец, IP-адрес захватывается функцией splitв скрипте, но не используется. Это можно легко добавить в хэш сеанса и использовать для печати однострочного резюме каждой пары входа и выхода.Формат датыTime::DateДля форматирования дат можно использовать модуль из той же коллекции.

Например:

  1. добавить use Date::Format;после use Date::Parse;строки

  2. Раскомментируйте $session{$s}->{IP} = $ip;цикл while(<>).

  3. Для распечатки данных используйте что-то вроде следующего:

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;
};

Вывод будет таким:

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

решение4

Это похоже на работу для dateutils. Выловите соответствующие части с помощью 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

Выход:

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

И передаем это в цикл while:

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

Выход:

user1   994
user2   1082
user3   0

Примечание: вы можете изменить переключатель ddiffкоманды -f, чтобы выбрать другой формат времени. Здесь мы используем прошедшие секунды.

Связанный контент