從檔案計算使用者的登入持續時間(以分鐘為單位)

從檔案計算使用者的登入持續時間(以分鐘為單位)

我在 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 的每個元素都是包含鍵userlogin和 的匿名雜湊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,這對於調試等非常有用libdata-dump-perl。否則,請使用cpan.

為了列印這些,我在腳本末尾添加了以下內容:

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

split最後,使用腳本中的函數捕獲 IP 位址但未使用。這可以輕鬆地添加到會話哈希中,並用於列印每個登入和登出對的一行摘要。這日期格式同一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開關以選擇不同的時間格式。這裡我們使用的是經過秒數。

相關內容