![Рассчитать длительность входа пользователя в систему в минутах из файла](https://rvso.com/image/192212/%D0%A0%D0%B0%D1%81%D1%81%D1%87%D0%B8%D1%82%D0%B0%D1%82%D1%8C%20%D0%B4%D0%BB%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C%20%D0%B2%D1%85%D0%BE%D0%B4%D0%B0%20%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%20%D0%B2%20%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%83%20%D0%B2%20%D0%BC%D0%B8%D0%BD%D1%83%D1%82%D0%B0%D1%85%20%D0%B8%D0%B7%20%D1%84%D0%B0%D0%B9%D0%BB%D0%B0.png)
У меня в системе 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
К вашему сведению, данные в %sessions
HoH выглядят так:
%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
Для форматирования дат можно использовать модуль из той же коллекции.
Например:
добавить
use Date::Format;
послеuse Date::Parse;
строкиРаскомментируйте
$session{$s}->{IP} = $ip;
циклwhile(<>)
.Для распечатки данных используйте что-то вроде следующего:
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
, чтобы выбрать другой формат времени. Здесь мы используем прошедшие секунды.