我试图从Apache日志中获取特定时间范围内的请求数量。我虽然使用sed
很容易做到这一点但是当我尝试对grep
做同样的事情时,我意识到grep
显示的结果比sed
更多。
这是我使用的grep
命令:
#grep '26/Apr/2017:08:0[0-2]:[0-2][0-4]' access.log
10.51.32.104 - - [26/Apr/2017:08:00:21 +0100] "GET / HTTP/1.1" 301 762 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
10.51.32.104 - - [26/Apr/2017:08:00:22 +0100] "GET /index.php?action=Login&module=Users HTTP/1.1" 200 6591 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
172.30.180.113 - - [26/Apr/2017:08:02:04 +0100] "GET / HTTP/1.0" 301 1906 "-" "Mozilla/4.0 (compatible; ipMonitor 10.7)"
172.30.180.113 - - [26/Apr/2017:08:02:04 +0100] "GET /index.php?action=Login&module=Users HTTP/1.0" 200 21951 "-" "Mozilla/4.0 (compatible; ipMonitor 10.7)"
这里是sed
命令:
#sed -n '/26\/Apr\/2017:08:00:21/ , /26\/Apr\/2017:08:02:04/p' access.log
10.51.32.104 - - [26/Apr/2017:08:00:21 +0100] "GET / HTTP/1.1" 301 762 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
10.51.32.104 - - [26/Apr/2017:08:00:22 +0100] "GET /index.php?action=Login&module=Users HTTP/1.1" 200 6591 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
172.30.180.113 - - [26/Apr/2017:08:02:04 +0100] "GET / HTTP/1.0" 301 1906 "-" "Mozilla/4.0 (compatible; ipMonitor 10.7)"
所以,正如您所看到的那样,它缺少172.30.180.113中与该模式匹配的一个访问权。
我做错了什么? sed
中的任何其他参数是否有帮助,或者有更好的方法吗?
答案 0 :(得分:3)
您非常接近使用sed
解决问题。这是一个良好的开端,我会鼓励你走这条路。
当然你可以使用regex
,但它有其局限性。考虑范围08:00
到09:59
,正则表达式很简单0[89]:[0-5][09]
。但如果范围为08:45
到09:30
,则regex
将不是您的朋友。因此,我鼓励您尝试使用该范围。
您使用sed
看到的限制是符合结束范围,sed
已停止处理。但我们知道会有更多的线落在最终范围内。
sed -n '/26\/Apr\/2017:08:00:21/,/26\/Apr\/2017:08:02:04/{p;b};/26\/Apr\/2017:08:02:04/p' access.log
分解sed命令:
/26\/Apr\/2017:08:00:21/,/26\/Apr\/2017:08:02:04/{p;b}
如果在范围内,则p
会对该行进行处理,然后b
ranch到sed
命令的末尾。
/26\/Apr\/2017:08:02:04/p
只有在前一个sed
命令的范围之外,才会执行此操作。这将处理范围内的额外行,但不在sed
的范围内。
awk
可以使用相同的技术。
awk '/26\/Apr\/2017:08:00:21/,/26\/Apr\/2017:08:02:04/{a=NR;print};a!=NR && /26\/Apr\/2017:08:02:04/{print}' access.log
第一个命令:
/26\/Apr\/2017:08:00:21/,/26\/Apr\/2017:08:02:04/{a=NR;print}
将打印范围内的行并将变量a
设置为NR
(当前记录号)的值。
第二个命令:
a!=NR && /26\/Apr\/2017:08:02:04/{print}
将打印范围内的其余行,但awk
被视为超出范围。
答案 1 :(得分:1)
正如评论中所提到的,您正在搜索一系列表达式,sed
将匹配从开头的第一个匹配到结束的第一个匹配的所有行。作为一种语言,awk
提供了比sed
更多的灵活性:
start=26/Apr/2017:08:00:21
end=26/Apr/2017:08:02:04
awk -v "s=$start" -v "e=$end" '$0~s{m=1} $0~e{m=0; f=1; print} f&&$0!~e{exit} m' access.log
我们有4个条件块。首先,我们在开始时检查匹配并设置m
。然后我们检查最后的匹配并取消设置m
,设置f
,然后继续打印。下一次检查是f
,只要最后没有匹配。这表明我们已经完成了结束字符串的所有匹配并且可以退出。最后一个块检查是否设置了m
,如果是,则打印。
同一程序的更详细版本:
awk -v "start_date=$start" -v "end_date=$end" '
{
if ($0 ~ start_date) {
matching = 1;
}
else if ($0 ~ end_date) {
matching = 0;
finishing = 1;
print $0;
}
else if (finishing) {
exit;
}
if (matching) {
print $0;
}
}
' access.log
感谢@alvits在评论中击败我,直到我找到了更好的解决方案!
答案 2 :(得分:1)
是的,有更好的方法(我在底部提到)。由于StackOverflow的建议不合适,我只是回答一下你所提供的代码中发生了什么。
您的grep
命令会打印与您指定的正则表达式匹配的每一行输入。虽然这有效,但有时很难在正则表达式中指定范围。 (您如何指定1月10日至3月2日的范围?)
sed
命令可能有点复杂。请考虑以下事项:
$ sed -n -e '/re/p'
这将打印与正则表达式re
匹配的所有行。基本上与grep
相同。
$ sed -n -e '/re1/,/re2/p'
这将打印从re1
的第一个匹配开始并以re2
的第一个匹配结束的所有行。这就是你问题中的sed脚本正在做的事情。请注意,这也有可能打印出与正则表达式之一不匹配的行:
$ printf 'one\ntwo\nthree\nfour\n' | sed -ne '/one/,/three/p'
one
two
three
如果您想使用sed提取日志中的行数,我建议采用其他方法。虽然sed
非常适合模式匹配,但它并不提供可以解释日期的工具。 Perl,或gawk,甚至bash将提供更多功能,并且在您需要更改代码的六个月后更容易理解/调试。