uniq 和 bash for 循环在标准输入关闭之前不写入标准输出(对于单行网站访客通知系统)

uniq 和 bash for 循环在标准输入关闭之前不写入标准输出(对于单行网站访客通知系统)

我正在尝试为网站的每个唯一访问者触发电脑扬声器上的蜂鸣声。

经过一番集思广益,似乎可以用一行字来实现:

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

但是,只要 stdin 打开,uniq 就不会输出任何内容(似乎等待 EOF)。 for 循环也是如此。如果我从链中删除 uniq,我仍然没有得到任何输出,而 tail 保持管道打开。

这似乎不是因为缓冲。即使我在运行此命令的情况下将超过 100.000 行写入测试文件,另一端也没有输出。

有没有一种方法可以在不完全破坏解决方案的美感(简单性)的情况下使其发挥作用?

更新

我解决了第一部分。 uniq 通过在 tail 命令前加上前缀来解除阻塞stdbuf -oL -eL(参见https://unix.stackexchange.com/a/25378/109296)。这对于循环不起作用。

更新2

我让它工作了 - 但不完全按照我的规范并且有两行:

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}'丢失是因为它在这个构造中不起作用(只是通过整行)。我不知道为什么。但我可以没有,因为无论如何 uniq结果证明没有那么有用,因为它只是看看邻近的行,这意味着请求模式 ip1、ip2、ip1 仍然会让 ip1 通过两次。 uniq -u会做我期望的事情,但它有同样的问题sort:只要标准输入打开就不会输出任何内容(即使是stdbuf -oL.

此命令只是将对基本 URL (/) 的所有请求写入另一个文件。我将其包装到一个循环中(并等待),以便在管道或连接由于某种原因中断时自动重试。

while inotifywait -e modify www.log; do beep -f 250; done 发出声音!我无法让 bash for 循环在无缓冲的情况下逐行处理,也尝试了while read相同的结果。因此我放弃并继续,inotifywait但这意味着我需要一个中间文件(也许命名管道也可以工作,没有尝试。对我来说并没有真正的区别)。

我仍然感谢那些有助于过滤唯一访客的贡献(不会增加复杂性)。

当我的团队成员返回办公室时,这将是一个很大的惊喜:-)

我计划扩展此通知系统以使用不同的音频来监视多个事件。对于一个积满灰尘的旧服务器来说,这是迄今为止我找到的最好的工作......

答案1

我想我明白你想要实现的目标:

  1. 对于网站的每次点击,由网络服务器记录:
  2. 如果访问是“唯一的”(您如何定义这个?)记录该条目并发送声音通知。

诀窍在于如何定义“独特”。是通过 URL、IP 地址还是 cookie?您使用 awk 的方法可以说是正确的方法,但是您被 shell 转义规则所困扰。

所以这里有一些结合了你们的方法的东西。首先,您确实需要 Web 服务器上的脚本来执行此操作。否则,您将迷失在复杂的引号转义规则中。其次,我假设您的网络服务器正在使用“通用日志格式”,坦率地说,这对于此类工作来说很糟糕,但我们可以使用它。

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

使用 mikeserv 关于 MAILFILE 的出色建议。 speedy 上的脚本应该如下所示:

#!/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 始终是行缓冲的。第一个 awk 确保您只获得实际的成功命中,而不是缓存命中或 404。 grep -o 仅打印输入的匹配部分,在本例中为 URL。 (这是 GNU grep,我假设您正在使用它。如果没有,请使用 stdbuf 技巧。)下一个 awk 使用一个小表达式有条件地打印出输入行 - 仅当该输入行以前从未见过时。

您还可以使用 perl 执行此操作,以在一个分支内实现更多复杂性:

#!/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; }'

现在这两个都只会打印唯一的 URL。如果来自不同 IP 的两个 Web 客户端访问同一页面怎么办?你只能得到一个输出。要改变这一点,使用 Perl 解决方案很简单:修改 url 中的键。

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

当使用 perl -a 时,$F[0] 代表输入的第一个空白分隔字段,就像 awk 的 $1 一样——即连接主机名/IP 地址。 Perl 的 $1 代表正则表达式的第一个匹配子表达式/\s"GET\s([^"]*)"\s/,即 URL 本身。神秘$F[$#F-1]意味着输入行的倒数第二个字段。

答案2

这就是我最终想到的,感谢 JJoao 提供的简洁的 Perl 命令:

# 终止时杀死所有内容
陷阱“kill 0”SIGINT SIGTERM
# 确保远程进程在退出时被终止,请参阅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) 并说 $F[0]'"; sleep 60; done > www.log ) &
(同时 inotifywait -e 修改 www.log;执行 beep -f 250;完成)&

相关内容