【问题标题】:Extracting timestamps from multiple lines in a log file with unix shell tools使用 unix shell 工具从日志文件中的多行中提取时间戳
【发布时间】:2014-04-21 19:17:42
【问题描述】:

输入是一个日志文件。我目前感兴趣的过程,在过程的开始和结束时记录一行。开始始终具有特定的固定模式以及对象 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

提前致谢!

【问题讨论】:

  • 输出的最后一行不是以4567开头吗?
  • 好收获。复制粘贴错误。我看看能不能编辑一下。
  • @user3426170 输出顺序重要吗?
  • 最好按照起始标签的顺序,或者按照起始标签的相反顺序。
  • 等等,确切地说:通过起始标签的时间戳。把空标签留在最后。

标签: unix awk sh


【解决方案1】:

您可以尝试使用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

【讨论】:

    【解决方案2】:

    输出的顺序与输入中出现的 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 存储在 ids 数组中的位置由numIds 的新值索引。由于seen[id]if 中后递增,因此下次看到idseen[id] 的值是1,因此条件!seen[id] 现在为假。

    这只是惯用的 awk 方法,用于按照它们在输入中出现的顺序保留唯一键列表 (ids),以便可以在 END 部分中按该顺序引用它们,而不是使用 @ 的随机顺序987654335@声明。

    【讨论】:

    • 我想我明白了大部分。首先提取时间戳、行类型(开始/结束)和 ID。然后,有一点我不太明白(seen-array)。然后,将时间添加到多维数组时间。 END 块按 ids 数组的顺序输出它。从变量名中,我可以推断出 seen-array 块检查之前是否遇到过 ID。但我不明白这实际上是如何工作的。您介意详细说明那个 if 语句吗?
    • 我在回答中添加了解释。
    【解决方案3】:

    在 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
    

    【讨论】:

    • 有趣,我从来没有想过bla bla bla 可能实际上是三个字,我只是假设它意味着一些随机文本。如果是 3 个字,那么事情肯定会变得简单得多。
    • 确实很有趣。我没有想到将那部分文本视为固定的。这会让事情变得更容易。也许下一次,我应该考虑我的输入实际上是多么可变,或者它是否真的是一个固定的字符串。谢谢!
    • 如果 ID 确实存在于特定字段中,那么 @BMW 的解决方案是可行的方法,但使用 if (!seen[c[2]]++) { b[++numIds] = c[2] } 语句进行调整以替换 b[c[2]] 并从我的解决方案更改为循环以保留排序,如果您不希望它是特定于 gawk 的,只需将每个 ][ 更改为 ,,因为它没有做任何您需要真正的 2D 数组的事情。
    • 如果你完全理解我的代码,id位置是否固定没什么大不了的。您可以轻松调整它。我在答案中更新了新的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-23
    • 2013-07-16
    • 1970-01-01
    • 2015-01-08
    相关资源
    最近更新 更多