linux - 使用 Sed 从日志文件中提取 XML 内容并将每个结果转储到不同的文件

标签 linux bash sed

我有以下 10 GB 的日志文件,我需要直接在 Unix 服务器上进行分析。

2017-12-12 13:04:28,716 [ABC] [DEF] DEBUG some message1
2017-12-12 13:04:28,716 [ABC] [DEF] DEBUG some message2
2017-12-12 13:04:28,716 [ABC] [DEF] DEBUG some message3
2017-12-12 13:04:28,716 [ABC] [DEF] DEBUG some message4
2017-12-12 13:04:28,716 [ABC] [DEF] DEBUG some message5
2017-12-12 13:04:28,732 [ABC] [DEF] DEBUG some message6
2017-12-12 13:04:28,732 [ABC] [DEF] DEBUG <xml>
<id>1</id> 
<!—- id is not unique since the XML data provides all the
information of an object X defined by its id at a specific point in time -->
some XML content on more than 500 lines
</xml>
2017-12-12 13:04:30,330 [ABC] [DEF] DEBUG some message8
2017-12-12 13:04:30,333 [ABC] [DEF] DEBUG some message9
2017-12-12 13:04:30,334 [ABC] [DEF] INFO some message10
2017-12-12 13:04:30,334 [ABC] [DEF] INFO some message11
2017-12-12 13:04:31,431 [ABC] [DEF] INFO some message12
2017-12-12 13:04:28,732 [ABC] [DEF] DEBUG <xml>
<id>2</id>
some XML content on more than 500 lines 
</xml>
2017-12-12 13:04:31,432 [ABC] [DEF] DEBUG some message13
2017-12-12 13:04:31,476 [ABC] [DEF] INFO some message14
2017-12-12 13:04:31,476 [ABC] [DEF] DEBUG some message14
2017-12-12 13:04:31,490 [ABC] [DEF] DEBUG some message15
2017-12-12 13:04:28,732 [ABC] [DEF] DEBUG <xml>
<id>1</id>
some XML content on more than 500 lines 
</xml>
2017-12-12 13:04:31,491 [ABC] [DEF] DEBUG some message16
2017-12-12 13:04:31,491 [ABC] [DEF] DEBUG some message17
2017-12-12 13:04:31,496 [ABC] [DEF] DEBUG some message18
2017-12-12 13:04:31,996 [ABC] [DEF] INFO some message19

为此,我想提取每条 XML 消息并将其转储到单独的文件中

例如:第一条 XML 消息将存储在 file1.xml 中,file2.xml 中的第二个, 等等。

如果必须将所有模式提取到一个文件中,那么直接使用如下内容会非常直接:

sed -n 's~<xml>(\s*\.*\s*)\s*</xml>~p' file.in > file.out #just a prototype

我考虑过一个解决方案,在该解决方案中我可以使用 <id> 的反向引用XML 的标记并使用它来命名我将转储它的文件,但它不起作用,因为 <id> 的值相同标记确实出现在日志文件中的不同位置,这将覆盖以前的提取。

sed -r 's~(<xml>…<id>(.*)</id>…</xml>)~echo "\1" >> \2.out~e' file.in #just a prototype

awk ,如果 XML 内容在一行中,它也会非常简单。然而,事实并非如此,我不知道我应该为 RS 定义哪个行分隔符。将 XML 内容视为单行并将其转储到单独的文件中。

awk ,我认为可行的是:

  • 首先识别<xml>日志中的起始标记并将测试变量更改为 yes
  • 在将 XML 的每一行转储到 file$i.out 之前将其存储在缓冲区变量中一旦我得到</xml> (当然还有将测试变量重置为 no )。

如果您有更好的解决方案 awksed 的解决方案我可以在其中访问一个包含当前正在处理的模式编号的变量,并重用它来生成输出文件,这会很棒。 (类似的东西:current_pattern_position 用于生成 file_$current_pattern_position.out )

I got already pretty interesting solutions using awk and perl. I would like to have a sed working solution for this case

最佳答案

更新:这是一种使用 Sed 的可移植、简化的方法:

#!/bin/sed -nf

# Execute the following group of commands for each line in the XML node to
# generate a series of shell commands that we'll feed into an interpreter:
/<xml>/,/<\/xml>/ {
    # Extract the ID number to generate a command that changes the output file:
    /^<id>\([0-9]\+\)<\/id>$/ {
        # Using the same pattern as above, substitute the ID number into a
        # command that updates the current output file and increments a counter
        # for the ID that we'll append as the filename extension:
        s//c\1=$(( c\1 + 1 )); exec > "file\1.$c\1"/
        # Output the generated command:
        p
        # Then, proceed to the next line:
        n
    }
    # Output any remaining lines in the XML block except for the <xml> tags:
    /<xml>\|<\/xml>/ !{
        # Escape any single quotes in the XML content (so we can wrap it in a
        # shell command below):
        s/'/'"'"'/g
        #'# (...ignore or remove this line...)
        # Generate a command that will write the line to the current file:
        s/^.*$/echo '&'/
        # Output the generated command:
        p
    }
}

正如我们所见,Sed 程序从输入中生成了一系列 shell 命令,我们可以将这些命令通过管道传输到 shell 解释器以写入输出文件:

$ sed -nf parse_log.sed < file.in | sh

这避免了过多的保留空间缓冲和 GNU Sed 的 e标志非常慢(每次我们需要写入文件时都需要生成一个子 shell 进程),并且使我们能够有效地跟踪遇到 ID 的次数,以便我们可以增加文件名中的数字。 Sed 还包含一个 w我们可以将标志附加到模式命令以更快地写入文件(而不是使用 e 进行脱壳),但我不知道有任何方法可以将变量参数传递给标志。

或者,我们可以将程序的内容作为 Sed 的参数。这是更容易粘贴的压缩版本:

sed -n '/<xml>/,/<\/xml>/ {                             
    /^<id>\([0-9]\+\)<\/id>$/{s//c\1=$(( c\1 + 1 ));exec > "file\1.$c\1"/;p;n;}
    /<xml>\|<\/xml>/!{'"s/'/'\"'\"'/g;"'s/^.*$/echo '"'&'"'/;p;}                
}' < file.in | sh

它有效,但我们可能可以看出 Sed 不是解决此问题的最佳工具。 Sed 的简单语言不是为这种逻辑设计的,因此代码并不漂亮,我们依赖 shell 生成文件,这增加了一些开销。如果您顽固地使用 Sed,那么这项工作可能需要更长的时间。对于对性能至关重要的事情,请考虑使用其他答案中描述的工具之一。

根据问题中的信息和示例,我假设我们不希望打开和关闭 <xml>输出中的标签,并且 ID 始终是一个单独一行的数字。该实现写入带有数字扩展名的文件名,当它找到重复的 ID 时该数字扩展名会递增(fileID.countfile1.1file1.2、 ETC。)。如果需要,更改这些详细信息应该很容易。


注意:如果需要,修订历史包含 two alternative implementations (一个使用 GNU Sed,另一个使用包装脚本)为了简洁起见我删除了。它们可以工作,但不必要地缓慢或复杂。

关于linux - 使用 Sed 从日志文件中提取 XML 内容并将每个结果转储到不同的文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47829079/

相关文章:

linux - 使用用户名、密码和命令执行 sudo su

linux - Linux 中的 request_mem_region()

linux - logrotate:即使我使用 -f 选项,每日轮换也不起作用

c# - Mono 3.2 中的行为与 Nancy Web 解决方案上的 DotLiquid 集合不一致

bash:如何传递包含特殊字符的密码

regex - 在文件中替换/插入时间戳

command-line - 如何使用 sed 只替换文件中的第一个匹配项?

python - 脚本执行时间[编辑]

Linux:检查文件描述符是否可供读取

regex - 使用 sed 处理带分隔符的文本文件