uniq e bash para loop não gravando para stdout antes de stdin closing (para sistema de notificação de visitante de site de uma linha)

4

Estou tentando acionar um bipe no alto-falante do PC para todos os visitantes únicos de um site.

Depois de alguns brainstorming, pareceu ser possível com uma linha:

for e in 'ssh me@mymachine "tail -n 1 -f /var/log/apache2/test.log | awk '{print $1}' | uniq"'; do beep; done

No entanto, o uniq não produz nada desde que o stdin esteja aberto (parece esperar pelo EOF). O mesmo vale para o loop for. Se eu remover o uniq da corrente, ainda não receberei saída com a cauda mantendo o tubo aberto.

Isto não parece ser devido a buffering. Mesmo que eu escreva > 100.000 linhas no arquivo de teste com este comando em execução, não há saída no outro lado.

Existe uma maneira de fazer isso funcionar sem matar completamente a beleza (simplicidade) da solução?

Atualizar

Eu resolvi a primeira parte. uniq é desbloqueado prefixando o comando tail com stdbuf -oL -eL (veja link ). O mesmo não funciona para o loop.

Atualização 2

Eu consegui trabalhar - mas não exatamente de acordo com minhas especificações e com 2 linhas:

while [ 1 -eq 1 ]; do ssh root@speedy "stdbuf -oL -eL tail -n 1 -f /var/log/apache2/www.access.log | stdbuf -oL -eL grep 'GET / '"; sleep 60; done > www.log

awk '{print $1}' está faltando porque não funcionou dentro dessa construção (acabou de passar por toda a linha). Eu não sei porque. Mas eu posso viver sem, porque de qualquer maneira uniq acabou por não ser tão útil, afinal, porque só olha para linhas adjacentes , o que significa que os padrões de pedidos ip1, ip2, ip1 ainda deixariam o ip1 passar duas vezes. uniq -u faria o que eu esperava, mas tem o mesmo problema como sort : não produz nada desde que stdin esteja aberto (nem mesmo com stdbuf -oL .

Este comando apenas escreve todos os pedidos para o URL base (/) para outro arquivo. Eu o envolvi em um loop (e aguarde) para que ele tentasse automaticamente se, por algum motivo, o pipe ou a conexão fossem interrompidos.

while inotifywait -e modify www.log; do beep -f 250; done faz o som! Eu não pude obter o bash para loop processar linha por linha unbuffered, também tentei while read com o mesmo resultado. Assim, desisti e continuei com inotifywait , o que significa que preciso de um arquivo intermediário (talvez um pipe nomeado também funcione, não tentei. Não faz diferença para mim).

Ainda agradeceria as contribuições que ajudam a fazer com que a filtragem de visitantes únicos funcione (sem aumentar a complexidade).

Esta será uma boa surpresa para os membros da minha equipe quando retornarem ao escritório: -)

Eu pretendo estender este sistema de notificação para monitorar vários eventos, usando diferentes freqüências de áudio. Esse é o melhor trabalho que encontrei até agora para um servidor antigo coletando poeira ...

    
por didi_X8 06.04.2015 / 01:40

2 respostas

0

Isto é o que eu finalmente consegui, graças ao comando Perl puro contribuído por JJoao:

# kill everything on termination
trap "kill 0" SIGINT SIGTERM
# Make sure the remote processes are killed on exit, see http://unix.stackexchange.com/questions/103699/kill-process-spawned-by-ssh-when-ssh-dies
shopt -s huponexit
( while [ 1 -eq 1 ]; do ssh -t -t root@speedy "stdbuf -oL -eL tail -n 1 -f /var/log/apache2/www.access.log | stdbuf -oL -eL grep 'GET / ' |  stdbuf -oL -eL perl -naE '($a{$F[0]}++ == 0) and say $F[0]'"; sleep 60; done > www.log ) &
( while inotifywait -e modify www.log; do beep -f 250; done ) &
    
por 07.08.2015 / 01:39
1

Acho que entendi o que você está tentando realizar:

  1. Para cada hit no site, que é registrado pelo servidor da web:
  2. Se a visita for "única" (como você define isso ??) registre a entrada e envie uma notificação audível.

O truque é como você define "exclusivo". É por URL, por endereço IP, por cookie? Sua abordagem com o awk foi, sem dúvida, o caminho certo a seguir, mas você foi preso por regras que escapam da concha.

Então, aqui está algo que combina suas abordagens. Primeiro, você realmente precisa de um script no servidor da Web para fazer isso. Caso contrário, você se perderá em regras complexas de escape de cotações. Segundo, estou assumindo que o seu servidor web está usando o "formato common-log", que, francamente, é uma droga para esse tipo de trabalho, mas podemos trabalhar com ele.

while true; do 
  ssh root@speedy remote-log-capturing-script
done > unique-visits.log

Use a excelente sugestão do mikeserv sobre o MAILFILE. O script no speedy deve ficar assim:

#!/bin/sh
tail -1f /var/log/apache2/www.access.log | 
awk '$(NF-1) == 200' | 
grep --line-buffered -o '"GET [^"]*"' |
awk '!url[$1]{ print; url[$1]=1 }'

Awk é sempre com buffer de linha. O primeiro awk garante que você está obtendo apenas hits reais bem-sucedidos, não em cache ou 404s. O grep -o imprime apenas a parte correspondente da entrada, neste caso, o URL. (Este é o grep do GNU, que eu suponho que você esteja usando. Se não, use o truque stdbuf.) O próximo awk usa uma pequena expressão para imprimir condicionalmente a linha de entrada - somente se essa linha de entrada nunca tivesse sido vista antes.

Você também pode fazer isso com o perl para obter mais complexidade em um fork:

#!/bin/sh
tail -1f /var/log/apache2/www.access.log | 
perl -lane '$|=1;' \
  -e 'if ($F[$#F-1] eq "200" and ' \
  -e ' /\s"GET\s([^"]*)"\s/ and !$url{$1}) { '\
  -e '  print $1;$url{$1}=undef; }'

Agora, os dois só imprimirão URLs únicos. E se dois clientes da web de diferentes IPs atingissem a mesma página? Você só recebe uma saída. Para mudar isso, com as soluções perl, isso é fácil: modifique a chave que vai para o url.

 $url{$F[0],$1}

Ao usar perl -a, $ F [0] representa o primeiro campo de entrada delimitado por espaços em branco, assim como o $ 1 de awk - isto é, o nome de host / endereço de conexão. E o $ 1 de perl representa a primeira subexpressão correspondente da expressão regular /\s"GET\s([^"]*)"\s/ , ou seja, apenas a própria URL. O enigmático $F[$#F-1] significa o segundo ao último campo da linha de entrada.

    
por 15.04.2015 / 12:53