使用unix shell工具从日志文件中的多行中提取时间戳

时间:2014-03-16 16:59:07

标签: unix awk sh

输入是一个日志文件。我目前感兴趣的过程,在过程的开始和结束时记录一行。开始始终具有某个固定模式以及对象ID。结尾也有一个固定的模式,以及相同的对象ID。

我希望输出每个对象ID包含一行,后跟第一行的时间戳,后跟第二行的时间戳。此输出将用于其他工具的进一步分析。输出应按起始行的时间戳排序;没有起始线(见障碍物)的物体应放在最后。

我想使用标准的Unix shell工具来解决这个问题。猜测,awk的东西应该可以解决问题。如果解决方案涉及Unix shell脚本,请使用sh作为shell。

障碍: 我无法保证进程是严格顺序的,因此在完全处理object1之前,object1的开始可以跟随object2的开始。此外,我不能保证日志文件始终与开头匹配,反之亦然。在这种情况下,ID应该具有缺失点的空值。

输入外观本质上是这样的:

2014-03-11 09:00:01.123 bla bla bla TAG_START ID:1234 bla bla bla
2014-03-11 09:00:11.123 bla bla bla TAG_END ID:1234 bla bla bla
2014-03-11 09:01:01.123 bla bla bla TAG_START ID:2353 bla bla bla
2014-03-11 09:02:01.123 bla bla bla TAG_END ID:2353 bla bla bla
2014-03-11 09:03:01.123 bla bla bla TAG_START ID:3456 bla bla bla
2014-03-11 09:04:01.123 bla bla bla TAG_END ID:4567 bla bla bla

输出:

1234;09:00:01.123;09:00:11.123
2353;09:01:01.123;09:02:01.123
3456;09:03:01.123;
4567;;09:04:01.123

提前致谢!

3 个答案:

答案 0 :(得分:2)

您可以尝试使用GNU awk(使用asorti函数进行排序输出):

gawk '
function findID(line) {
    for (i = 1; i<=NF; i++)
    if ($i ~ /^ID/)
        split($i, tmp, /:/)
        return tmp[2]
}
/TAG_START/ {
    id = findID($0)
    lines[id] = $2 ";"
}
/TAG_END/ {
    id = findID($0)
    lines[id] = ((lines[id]) ? lines[id] $2 : ";" $2)
}
END {
    n = asorti(lines, lines_s)
    for (i = 1; i <= n; i++) {
        print lines_s[i] ";" lines[lines_s[i]]
    }
}' file

如果您没有GNU awk,那么您可以将常规awk的输出传输到sort

awk '
function findID(line) {
    for (i = 1; i<=NF; i++)
    if ($i ~ /^ID/)
        split($i, tmp, /:/)
        return tmp[2]
}
/TAG_START/ {
    id = findID($0)
    lines[id] = $2 ";"
}
/TAG_END/ {
    id = findID($0)
    lines[id] = ((lines[id]) ? lines[id] $2 : ";" $2)
}
END {
    for (x in lines)
        print x ";" lines[x]
}' file | sort -t";" -nk1,2

<强>输出:

1234;09:00:01.123;09:00:11.123
2353;09:01:01.123;09:02:01.123
3456;09:03:01.123;
4567;;09:04:01.123

<强>解释

  • 对于有/TAG_START/的行,我们调用用户定义的函数,迭代空格分隔的每个字段。一旦我们遇到以ID开头的字段,我们会将其与:分隔符拆分并捕获其第二部分(即如果字段为TAG_START ID:1234,我们会捕获1234
  • 我们将其用作数组lines中的键,并为其分配第二个字段的值,即时间戳,并在其后填充;
  • 我们对有/TAG_END/的行执行类似的操作,唯一的区别是我们检查数组中是否存在key。如果它存在,我们将第二个字段附加到它,因为它是结束时间戳。如果密钥不存在,那么我们只需添加;并将值添加到数组中。这是为了满足您的要求另外,我不能保证日志文件始终与开头匹配,反之亦然。在这种情况下,ID应该有缺失点的空值。
  • 对于GNU awk,我们调用asorti函数按值排序并迭代数组并打印行。对于常规awk,我们会打印这些行并将其传递给sort

答案 1 :(得分:1)

输出的顺序与输入中显示的ID相同:

awk -v OFS=';' '
{
    time = $2

    type = (/TAG_START ID:/ ? "s" : "e")

    sub(/.*TAG_(START|END) ID:/,"")
    sub(/ .*$/,"")
    id = $0

    if (!seen[id]++) {
        ids[++numIds] = id
    }

    times[id,type] = time
}
END {
    for (idNr=1; idNr<=numIds; idNr++) {
        id = ids[idNr]
        print id, times[id,"s"], times[id,"e"]
    }
}' file
1234;09:00:01.123;09:00:11.123
2353;09:01:01.123;09:02:01.123
3456;09:03:01.123;
4567;;09:04:01.123

if语句只是按输入文件中显示的顺序跟踪唯一ID。第一次看到id时,数组seen[id]的值为零,因为这是一个新的唯一ID,因此计数器numIds预先递增,id存储在{ {1}}数组位于由新值ids索引的位置。由于numIdsseen[id]中后递增,因此下次看到ifid的值为seen[id],因此条件为1现在是假的。

它只是习惯性的awk方法,用于如何按照它们在输入中出现的顺序保留唯一键列表(!seen[id]),以便它们可以在END部分中按顺序引用使用ids语句的随机顺序。

答案 2 :(得分:1)

在gnu awk中使用arrays of arrays

awk '{split($7,c,":");a[c[2]][$6]=$2;b[c[2]]}
END{for (i in b) {print i,a[i]["TAG_START"],a[i]["TAG_END"]}}' OFS=";" file

1234;09:00:01.123;09:00:11.123
2353;09:01:01.123;09:02:01.123
3456;09:03:01.123;
4567;;09:04:01.123

解释

  • 示例$ 7是ID:1234,拆分为数组c,并使用值c [2]作为数组a中的索引。
  • 使用arrays of arrays,您可以直接打印两个值a[i]["TAG_START"]a[i]["TAG_END"]

如果ID位置未修复,则为新版本。

awk '{for (i=1;i<=NF;i++) if ($i ~/TAG_(START|END)/) {status=$i;id=$(i+1)};split(id,c,":");a[c[2]][status]=$2;b[c[2]]}
END{for (i in b) {print i,a[i]["TAG_START"],a[i]["TAG_END"]}}' OFS=";" file