Mur*_*dam 4 awk text-processing time
我在 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
Run Code Online (Sandbox Code Playgroud)
我正在尝试计算每个用户在几分钟内登录和注销之间花费的时间。每个用户只有一次登录/注销,我想一次为所有用户生成一份报告。
我尝试过的:
我试图先提取用户:
users=$(awk -v RS=" " '/login/{getline;print $0}' data)
Run Code Online (Sandbox Code Playgroud)
它返回用户(登录),然后我尝试提取他们登录的时间,但我目前被卡住了。任何帮助,将不胜感激!
编辑:我能够让用户和日期执行以下操作:
users=$(grep -o 'user[0-9]' data)
dates=$(grep -o '[0-2][0-9]:[0-5][0-9]:[0-5][0-9]' data)
Run Code Online (Sandbox Code Playgroud)
如果我找到完整的解决方案,我会在这里分享。
尽管该站点“不是脚本编写服务”;),但这是一个不错的小练习,因此我将提出以下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)
}
}
Run Code Online (Sandbox Code Playgroud)
这依赖于使用 GNUdate
命令(GNU/Linux 系统上标准工具套件的一部分)和日志文件中指定的时间格式。另请注意,这不包含许多安全检查,但您应该了解如何根据需要对其进行修改。
sys-log
开头和结尾附近的字符串的行,Log
以提高选择性,以防可能有其他内容。如前所述,这是一个非常基本的测试,但同样,您可以了解如何使其更具体。date
调用 viasprintf
并将任务委托给 shell ,操作的时间戳将转换为“自纪元以来的秒数” 。Login
,则时间戳存储在数组中login
,用户名为“数组索引”。Logout
,则将计算持续时间并将其添加到logtime
包含到目前为止所有用户的总日志时间的数组中。logtime
并通过简单除法将日志时间从秒转换为分钟来生成报告。你可以通过调用它
awk -f calc_logtime.awk logfile.dat
Run Code Online (Sandbox Code Playgroud)
使用用于时间函数和 gensub() 以及数组数组的 GNU awk:
$ 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]
}
Run Code Online (Sandbox Code Playgroud)
$ awk -f tst.awk file
user1 16.57
user2 18.03
user3 0.00
Run Code Online (Sandbox Code Playgroud)
这将比date
从 awk调用 Unix 的运行速度快几个数量级,因为后者每次都必须生成一个子外壳才能这样做。
如果您还想在运行脚本时获取已登录但尚未注销的用户的报告,例如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
Run Code Online (Sandbox Code Playgroud)
然后只需调整脚本:
$ 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"
}
}
Run Code Online (Sandbox Code Playgroud)
$ awk -f tst.awk file
user1 16.57 Complete
user2 18.03 Complete
user3 0.00 Complete
user4 51.10 Partial
Run Code Online (Sandbox Code Playgroud)
如果您需要查找用户在已经登录但没有注销的情况下再次登录的情况,或者在没有关联登录的情况下以不同的方式处理注销或执行其他任何操作,那么这也只是微不足道的调整。
以下perl
脚本使用TimeDate集合中的Date::Parse模块来解析每个记录中的日期和时间,而不是依赖于 GNU 日期来执行此操作。这可能是为您的发行版打包的(在 debian 上),否则使用.apt install libtimedate-perl
cpan
该脚本的工作原理是使用每个输入行的最后一个字段(它似乎是一个会话 ID)作为一个名为 的哈希值 (HoH) 数据结构的顶级键%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);
};
Run Code Online (Sandbox Code Playgroud)
将其另存为,例如,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
Run Code Online (Sandbox Code Playgroud)
仅供参考,%sessions
HoH 中的数据如下所示:
%sessions = {
13272 => { login => 1620424730, logout => 1620424730, user => "user3" },
13473 => { login => 1620292323, logout => 1620293317, user => "user1" },
14195 => { login => 1620292526, logout => 1620293608, user => "user2" },
}
Run Code Online (Sandbox Code Playgroud)
会话完全有可能没有登录或注销时间戳。如果缺少任何一个,很容易向 STDERR 打印一条消息。或者以您选择的方式处理此类异常。上面的脚本只是忽略了它们。
为了完整起见,数据%users
最终看起来像这样:
%users = { user1 => 994, user2 => 1082, user3 => 0 }
Run Code Online (Sandbox Code Playgroud)
顺便说一句,这些数据结构是用Data::Dump模块打印的,这对于调试等非常有用。Debian 包名称是libdata-dump-perl
,其他发行版可能有。否则,使用cpan
.
为了打印这些,我在脚本末尾添加了以下内容:
use Data::Dump qw(dump);
print "%sessions = ", dump(\%sessions);
print "%users = ", dump(\%users)
Run Code Online (Sandbox Code Playgroud)
最后,split
使用脚本中的函数捕获 IP 地址但未使用。这可以很容易地添加到会话哈希中,并用于打印每个登录和注销对的一行摘要。来自同一集合的Date::Format模块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;
};
Run Code Online (Sandbox Code Playgroud)
输出将是这样的:
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
Run Code Online (Sandbox Code Playgroud)