ファイル内のユーザーのログイン期間を分単位で計算します。

ファイル内のユーザーのログイン期間を分単位で計算します。

私の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)
    }
}

dateこれは、GNUコマンド(GNU / Linuxシステムの標準ツールバーの一部)の使用とログファイルで指定された時間形式によって異なります。また、これには多くのセキュリティチェックは含まれていませんが、必要に応じて修正する方法を知っておく必要があります。

  • 次を含む行を探します。両方sys-logLog何か他のものがある場合は、選択性を高めるために始めと終わりに近い文字列です。前述のように、これは非常に基本的なテストですが、もう一度具体的にする方法を学ぶことができます。
  • ユーザーは、行からスペースで区切られた5番目のフィールドに抽出されます。
  • ジョブは、行のスペースで区切られた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

これは、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

次にスクリプトを調整します。

$ 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として表されます)をHash-Hash(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)

split最後に、スクリプトの関数を使用してIPアドレスをキャプチャしますが、使用しないでください。これはセッションハッシュに簡単に追加でき、各ログインとログアウトのペアの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スイッチを変更して別の時間形式を選択できます。ここでは経過した秒を使用しています。

関連情報