ファイルからユーザーのログオン時間を分単位で計算する

ファイルからユーザーのログオン時間を分単位で計算する

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

また、各ユーザーがログインしてからログアウトするまでの時間を分単位で計算しようとしています。ログイン/ログアウトはユーザーごとに 1 回のみであり、すべてのユーザーのレポートを一度に生成したいと考えています。

私が試したこと:

まず最初にユーザーを抽出しようとしました:

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末尾近くを追加して選択性を高めます。 前述のように、これは非常に基本的なテストですが、これをより具体的にする方法のアイデアを得ることができます。
  • ユーザーは、行の 5 番目のスペース区切りフィールドとして抽出されます。
  • アクションは、行の 7 番目のスペース区切りフィールドとして抽出されます。
  • アクションのタイムスタンプは、dateを介して呼び出しを生成しsprintf、タスクをシェルに委任することによって、「エポックからの秒数」に変換されます。
  • アクションが の場合、タイムスタンプは、ユーザー名を「配列インデックス」としてLogin配列 に保存されます。login
  • アクションが の場合、期間が計算され、これまでのすべてのユーザーの合計ログ時間を含むLogout配列に追加されます。logtime
  • ファイルの終わりでは、すべての「配列インデックス」を反復処理しlogtime、単純な除算によってログ時間を秒から分に変換してレポートが生成されます。

以下の方法で呼び出すことができます

awk -f calc_logtime.awk logfile.dat

答え2

時間関数と 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]
}

$ awk -f tst.awk file
user1 16.57
user2 18.03
user3 0.00

これは、awk から 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スクリプトは、日付::解析モジュールから時刻日付GNU date に頼る代わりに、各レコードから日付と時刻を解析するコレクション。これはおそらくディストリビューション (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 }

ちなみに、これらのデータ構造は、データ::ダンプモジュールは、デバッグなどに非常に便利です。Debian パッケージ名は でlibdata-dump-perl、他のディストリビューションにはおそらく含まれています。それ以外の場合は、 を使用してインストールしますcpan

これらを印刷するには、スクリプトの最後に次のコードを追加しました。

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

最後に、IPアドレスはsplitスクリプト内の関数で取得されますが、使用されません。これは簡単にセッションハッシュに追加でき、ログインとログアウトのペアごとに1行の要約を印刷するために使用できます。日付::フォーマット同じ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スイッチを変更して、別の時間形式を選択できます。ここでは、経過秒数を使用しています。

関連情報