我在 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)
}
}
这依赖于使用 GNUdate
命令(GNU/Linux 系统上标准工具套件的一部分)以及日志文件中指定的时间格式。另请注意,这不包含很多安全检查,但您应该了解如何根据您的需要对其进行修改。
- 它将查找包含的行两个都
sys-log
靠近开头和Log
结尾的字符串以增加选择性,以防万一可能有其他内容。如前所述,这是一个非常基本的测试,但同样,您可以了解如何使其更具体。 - 用户将被提取为该行的第五个空格分隔字段。
- 该动作将被提取为该行的第七个空格分隔字段。
date
通过生成调用sprintf
并将任务委托给 shell,操作的时间戳将转换为“自纪元以来的秒数” 。- 如果操作是
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
这将比date
从 awk 调用 Unix 运行速度快几个数量级,因为后者每次都必须生成一个子 shell。
如果您还想在运行脚本时获得已登录但尚未注销的用户的报告,例如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
脚本使用日期::解析模块从时间日期集合来解析每个记录中的日期和时间,而不是依赖 GNU 日期来完成此操作。这可能是为您的发行版打包的(在 debian 上apt install libtimedate-perl
),否则使用cpan
.
该脚本的工作原理是使用每个输入行的最后一个字段(似乎是会话 ID)作为名为 的哈希哈希 (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
仅供参考,HoH 中的数据%sessions
如下所示:
%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 }
顺便说一句,这些数据结构是用数据::转储module,这对于调试等非常有用。Debian 软件包名称是libdata-dump-perl
,其他发行版可能有它。否则,请使用cpan
.
为了打印这些,我在脚本末尾添加了以下内容:
use Data::Dump qw(dump);
print "%sessions = ", dump(\%sessions);
print "%users = ", dump(\%users)
split
最后,使用脚本中的函数捕获 IP 地址但未使用。这可以很容易地添加到会话哈希中,并用于打印每个登录和注销对的一行摘要。这日期格式同一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
开关以选择不同的时间格式。这里我们使用的是经过秒数。