【问题标题】:How to filter logs easily with awk?如何使用 awk 轻松过滤日志?
【发布时间】:2016-03-22 12:53:42
【问题描述】:

假设我有一个这样的日志文件mylog

[01/Oct/2015:16:12:56 +0200] error number 1
[01/Oct/2015:17:12:56 +0200] error number 2
[01/Oct/2015:18:07:56 +0200] error number 3
[01/Oct/2015:18:12:56 +0200] error number 4
[02/Oct/2015:16:12:56 +0200] error number 5
[10/Oct/2015:16:12:58 +0200] error number 6
[10/Oct/2015:16:13:00 +0200] error number 7
[01/Nov/2015:00:10:00 +0200] error number 8
[01/Nov/2015:01:02:00 +0200] error number 9
[01/Jan/2016:01:02:00 +0200] error number 10

我想找出 10 月 1 日 18 点到 11 月 1 日 1 点之间出现的那些行。也就是说,预期的输出是:

[01/Oct/2015:18:07:56 +0200] error number 3
[01/Oct/2015:18:12:56 +0200] error number 4
[02/Oct/2015:16:12:56 +0200] error number 5
[10/Oct/2015:16:12:58 +0200] error number 6
[10/Oct/2015:16:13:00 +0200] error number 7
[01/Nov/2015:00:10:00 +0200] error number 8

我已经设法通过使用match()mktime() 将时间转换为时间戳。第一个找到指定的模式,该模式存储在数组a[] 中,因此可以访问(有趣的是,看看格伦杰克曼对access captured group from line pattern 的回答是一个很好的例子)。由于mktime 需要YYYY MM DD HH MM SS[ DST] 格式,我还必须将Xxx 格式的月份转换为数字,为此我使用an answer by Ed Morton to "convert month from Aaa to xx"awk '{printf "%02d\n",(match("JanFebMarAprMayJunJulAugSepOctNovDec",$0)+2)/3}'

一起,最后我在变量mytimestamp中得到了时间戳:

awk 'match($0, /([0-9]+)\/([A-Z][a-z]{2})\/([0-9]{4}):([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) ([+-][0-9]{4})/, a) {
        day=a[1]; month=a[2]; year=a[3];
        hour=a[4]; min=a[5]; sec=a[6]; utc=a[7];
        month=sprintf("%02d",(match("JanFebMarAprMayJunJulAugSepOctNovDec",month)+2)/3);
        mydate=sprintf("%s %s %s %s %s %s %s", year,month,day,hour,min,sec,utc);
        mytimestamp=mktime(mydate)
        print mytimestamp
    }' mylog

返回:

1443708776
1443712376
1443715676

等等

所以现在我已准备好根据给定日期进行转换。由于awk 处理这种格式需要很多时间,我更喜欢通过外部shell 变量提供它们,使用date -d"my date" +"%s" 打印时间戳:

start="$(date -d"1 Oct 2015 18:00 +0200" +"%s")"
end="$(date -d"1 Nov 2015 01:00 +0200" +"%s")"

总的来说,这是可行的:

awk start="$(date -d"1 Oct 2015 18:00 +0200" +"%s")" end="$(date -d"1 Nov 2015 01:00 +0200" +"%s")" 'match($0, /([0-9]+)\/([A-Z][a-z]{2})\/([0-9]{4}):([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) ([+-][0-9]{4})/, a) {day=a[1]; month=a[2]; year=a[3]; hour=a[4]; min=a[5]; sec=a[6]; utc=a[7]; month=sprintf("%02d",(match("JanFebMarAprMayJunJulAugSepOctNovDec",month)+2)/3); mydate=sprintf("%s %s %s %s %s %s %s", year,month,day,hour,min,sec,utc); mytimestamp=mktime(mydate); if (start<=mytimestamp && mytimestamp<=end) print}' mylog
[01/Oct/2015:18:07:56 +0200] error number 3
[01/Oct/2015:18:12:56 +0200] error number 4
[02/Oct/2015:16:12:56 +0200] error number 5
[10/Oct/2015:16:12:58 +0200] error number 6
[10/Oct/2015:16:13:00 +0200] error number 7
[01/Nov/2015:00:10:00 +0200] error number 8

但是,对于应该更直截了当的事情来说,这似乎是一项相当多的工作。尽管如此,man gawk 中“时间函数”部分的介绍是

由于 AWK 程序的主要用途之一是处理日志文件 包含时间戳信息,gawk 提供以下 获取时间戳和格式化它们的函数。

所以我想知道:有没有更好的方法来做到这一点?例如,如果格式而不是 dd/Mmm/YYYY:HH:MM:ssdd Mmm YYYY HH:MM:ss 会怎样?难道不能在外部提供匹配模式,而不是每次发生这种情况时都必须更改它吗?我真的必须使用match(),然后处理该输出以提供mktime()吗? gawk 不是提供了更简单的方法吗?

【问题讨论】:

  • 您好,我不熟悉 awk 或 gawk,来到这里是因为 regex 标记并发现您的问题很有趣。虽然我熟悉 .bat 编程,但在这种情况下,我们使用操作系统定义的变量来处理这类事情。是否可以将环境变量与 awk 的参数混合在一起?
  • @JorgeCampos 感谢您的评论。是的,在awk 中,您可以使用环境变量。例如你可以说awk -v myvar="$shell_var" 'BEGIN{print myvar}' 来打印一个shell 变量。看-v的用法就可以通过了。
  • 这不是您的问题的解决方案吗?当然,如果没有更好的方法。
  • @JorgeCampos mmm 是的,这实际上是我的问题之一:我可以在match() 函数外部提供这样的日期格式参数吗?
  • 根据文档,不,你不能。我看到的唯一方法是您使用外部变量。但正如我所说,我不是 awk 专家。也许其他人知道方法!

标签: regex date awk timestamp gawk


【解决方案1】:

使用 ISO 8601 时间格式!

但是,对于应该更直接的事情来说,这似乎是一项相当多的工作。

是的,这应该是直截了当的,之所以没有,是因为日志没有使用ISO 8601。应用程序日志应使用 ISO 格式和 UTC 来显示时间,其他设置应视为损坏和修复。

您的请求应分为两部分。第一部分规范日志,将日期转换为 ISO 格式,第二部分进行研究:

awk '
match($0, /([0-9]+)\/([A-Z][a-z]{2})\/([0-9]{4}):([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}) ([+-][0-9]{4})/, a) {
  day=a[1]
  month=a[2];
  year=a[3]
  hour=a[4]
  min=a[5]
  sec=a[6]
  utc=a[7];
  month=sprintf("%02d", (match("JanFebMarAprMayJunJulAugSepOctNovDec",month)+2)/3);
  myisodate=sprintf("%4d-%2d-%2dT%2d:%2d:%2d%6s", year,month,day,hour,min,sec,utc);
 $1 = myisodate
 print
}' mylog

ISO 8601 日期的好处——除了它们是一个标准——是时间顺序与字典顺序一致,因此,您可以使用/…/,/…/ 运算符来提取您所在的日期感兴趣。例如,要查找 2015 年 10 月 1 日 18:00 +02002015 年 11 月 1 日 01:00 +0200 之间发生的事情,请将以下过滤器附加到上一个过滤器,标准化过滤器:

awk '/2015-10-01:18:00:00+0200/,/2015-11-01:01:00:00+0200/'

【讨论】:

  • 你能回答我stackoverflow.com/questions/39853960/…的这个问题吗?我有一个价值 100 的公开赏金 :)
  • 我的日志文件中的日期格式有点不同。我尝试从这个问题中给出的日期格式开始,通过创建一个日志文件,其内容与问题中给出的相同,并尝试像这样运行awk 命令 - awk &lt;your command&gt; &lt;path to log file&gt;,但我没有得到任何输出。
【解决方案2】:

无需进入时间格式(假设所有记录的格式相同),您可以使用sort | awk 组合轻松实现相同的目的。

这假设日志没有排序,根据您的格式和特殊排序选项对月份进行排序 (M) 和 awk 选择感兴趣的范围。排序是按年、月、日的顺序排列的。

$ sort -k1.9,1.12 -k1.5,1.7M -k1.2,1.3 log | awk '/01\/Oct\/2015/,/01\/Nov\/2015/'

如果文件已经排序,您也可以轻松扩展以包含时间并删除排序。

以下也有时间限制

awk -F: '/01\/Oct\/2015/ && $2>=18{p=1} 
         /01\/Nov\/2015/ && $2>=1 {p=0} p'

【讨论】:

  • 请注意,这比我在问题中使用的更通用,而且非常具体。我的意思是,它有效,我感谢您的努力,但无助于概括问题并提供一个很好的工具来过滤具有给定格式和两个给定日期时间的日志。
  • 为什么需要使用两种不同的时间格式?如果您可以在日志中使用相同的格式,那么脚本将是微不足道的。
【解决方案3】:

我会在awk 中使用date 命令来实现这一点,但不知道这将如何处理大型日志文件。

awk -F "[][]" -v start="$(date -d"1 Oct 2015 18:00 +0200" +"%s")"
    -v end="$(date -d"1 Nov 2015 01:00 +0200" +"%s")" '{
        gsub(/\//,"-",$2);sub(/:/," ",$2);
        cmd="date -d\""$2"\" +%s" ;
        cmd|getline mytimestamp;
        close(cmd);
        if (start<=mytimestamp && mytimestamp<=end) print
}' mylog

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多