私の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-log
Log
何か他のものがある場合は、選択性を高めるために始めと終わりに近い文字列です。前述のように、これは非常に基本的なテストですが、もう一度具体的にする方法を学ぶことができます。 - ユーザーは、行からスペースで区切られた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
内のモジュールを使用して日付形式を指定できます。
たとえば、
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
スイッチを変更して別の時間形式を選択できます。ここでは経過した秒を使用しています。