Eu tenho um arquivo em um sistema Linux parecido com o seguinte:
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
E estou tentando calcular o tempo que cada usuário gastou entre o login e o logout em minutos. Haverá apenas um login/logout por usuário e quero gerar um relatório para todos os usuários de uma vez.
O que eu tentei:
Tentei primeiro extrair os usuários primeiro:
users=$(awk -v RS=" " '/login/{getline;print $0}' data)
que retorna os usuários (logados), e então tento extrair o horário em que eles fizeram login, mas no momento estou travado. Qualquer ajuda seria apreciada!
Editar: consigo obter usuários e datas fazendo o seguinte:
users=$(grep -o 'user[0-9]' data)
dates=$(grep -o '[0-2][0-9]:[0-5][0-9]:[0-5][0-9]' data)
Se eu encontrar uma solução completa, compartilharei aqui.
Responder1
Embora este site "não seja um serviço de escrita de scripts" ;), este é um pequeno exercício interessante, então proporei o seguinte awk
programa. Você pode salvá-lo em um arquivo 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)
}
}
Isso depende do uso do comando GNU date
(parte do conjunto de ferramentas padrão em sistemas GNU/Linux) e do formato de hora em seu arquivo de log conforme especificado. Observe também que isso não contém muitas verificações de segurança, mas você deve ter uma ideia de como modificá-lo de acordo com suas necessidades.
- Ele irá procurar por linhas que contenhamambosa string
sys-log
perto do início eLog
perto do fim para aumentar a seletividade, caso haja outro conteúdo. Como afirmado, este é um teste muito rudimentar, mas, novamente, você pode ter uma ideia de como torná-lo mais específico. - O usuário será extraído como o quinto campo separado por espaço da linha.
- A ação será extraída como o 7º campo separado por espaço da linha.
- O carimbo de data/hora da ação será convertido em "segundos desde a época" gerando uma
date
chamadasprintf
e delegando a tarefa ao shell. - Se a ação for
Login
, o carimbo de data/hora é armazenado em um arraylogin
, com o nome de usuário como "índice do array". - Se a ação for
Logout
, a duração será calculada e adicionada a uma matrizlogtime
contendo o tempo total de log de todos os usuários até o momento. - No final do arquivo, um relatório será gerado, iterando todos os "índices de array"
logtime
e convertendo os tempos de log de segundos para minutos por divisão simples.
Você pode ligar através
awk -f calc_logtime.awk logfile.dat
Responder2
Com GNU awk para funções de tempo e gensub() e matrizes de matrizes:
$ 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
Isso executará ordens de magnitude mais rápido do que chamar o Unix date
do awk, já que o último precisa gerar um subshell toda vez para fazer isso.
Se você também deseja obter um relatório de usuários que efetuaram login, mas não efetuaram logout, ao executar o script, por exemplo, user4
neste arquivo de entrada modificado:
$ 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
Depois é só ajustar o script:
$ 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
Se você precisasse encontrar casos em que um usuário efetuou login novamente enquanto já estava conectado sem nenhum logout no meio ou lidar com logouts sem login associado de maneira diferente ou fazer qualquer outra coisa, então tudo isso também seria apenas ajustes triviais.
Responder3
O perl
script a seguir usa oData::Analisarmódulo doHoraDatacoleção para analisar as datas e horas de cada registro em vez de depender da data GNU para fazer isso. Provavelmente está empacotado para sua distribuição (no debian apt install libtimedate-perl
), caso contrário, instale-o com cpan
.
O script funciona usando o último campo de cada linha de entrada (que parece ser um ID de sessão) como a chave de nível superior para uma estrutura de dados Hash-of-Hashes (HoH) chamada %sessions
. Cada elemento de %sessions é um hash anônimo contendo as chaves user
, login
e logout
.
Depois que todo o arquivo tiver sido lido e analisado, os totais cumulativos de cada usuário são calculados (e armazenados em outra matriz associativa, %users
) e depois impressos. A saída é classificada por nome de usuário.
#!/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);
};
Salve-o como, por exemplo, login-times.pl
e torne-o executável com a extensão chmod +x login-times.pl
. Execute como:
$ ./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
Para sua informação, os dados no %sessions
HoH são assim:
%sessions = {
13272 => { login => 1620424730, logout => 1620424730, user => "user3" },
13473 => { login => 1620292323, logout => 1620293317, user => "user1" },
14195 => { login => 1620292526, logout => 1620293608, user => "user2" },
}
É perfeitamente possível que uma sessão não tenha um carimbo de data/hora de login ou logout. Seria fácil imprimir uma mensagem para STDERR se alguma delas estivesse faltando. Ou para lidar com essas anomalias da maneira que você escolher. O script acima simplesmente os ignora.
Para completar, os dados %users
acabam ficando assim:
%users = { user1 => 994, user2 => 1082, user3 => 0 }
Aliás, essas estruturas de dados foram impressas com oDados::Despejomódulo, que é muito útil para depuração, etc. O nome do pacote Debian é libdata-dump-perl
, outras distros provavelmente o possuem. Caso contrário, instale-o com cpan
.
Para imprimi-los, adicionei o seguinte no final do script:
use Data::Dump qw(dump);
print "%sessions = ", dump(\%sessions);
print "%users = ", dump(\%users)
Finalmente, o endereço IP é capturado com a split
função no script, mas não é usado. Isso pode ser facilmente adicionado ao hash da sessão e usado para imprimir um resumo de uma linha de cada par de login e logout. OFormato de datamódulo da mesma Time::Date
coleção pode ser usado para formatar as datas.
Por exemplo:
adicione
use Date::Format;
depois dause Date::Parse;
linhaRemova o comentário
$session{$s}->{IP} = $ip;
nowhile(<>)
loop.Use algo como o seguinte para imprimir os dados:
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;
};
A saída seria como:
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
Responder4
Isso parece um trabalho para dateutils
. Pesque as peças relevantes com 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
Saída:
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
E canalize para um loop 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
Saída:
user1 994
user2 1082
user3 0
Nota: você pode alterar a opção ddiff
do comando -f
para escolher um formato de hora diferente. Aqui estamos usando segundos decorridos.