1

我正在使用以下内容提取 XML 标记之间的内容: -

perl -lne 'BEGIN{undef $/} while (/<tagname>(.*?)<\/tagname>/sg){print $1}' input.txt > output.txt

不幸的是我遇到了out of memory问题,我知道我可以拆分文件并处理每个 concat 但我想知道是否还有另一种方法,是对上面的修改还是使用 awk 或 sed 之类的?

文件大小在input.txt17GB 和 70GB 之间变化。

编辑:

输入文件可以是任何 XML 文件,需要注意的是它不包含换行符,例如:-

<body><a></a><b></b><c></c></body><foo></foo><bar><z></z></bar>

4

6 回答 6

3

这个单行将整个文件作为一个巨大的“行”读入内存。当然,在内存中塞满 17GB 甚至更多的内存会出现问题!逐行读取和处理文件或用于read获取合适大小的块。

在这种情况下,搜索<tagname>,记下它在行中的位置并从那里开始搜索结束标记。如果您没有找到它,请将当前行/块填充到缓冲区中并重复,直到您在文件中的其他行上找到它。找到后,打印出此缓冲区并将其清空。重复直到文件结束。

请注意,如果您使用任意大小的块,则必须通过从块的末尾切割不完整的标签并将其填充到“处理”缓冲区中来考虑标签被边界分割的可能性。

于 2012-08-22T10:19:00.890 回答
3

使用像XML::LibXML::Reader这样的 pull-parser 应该可以解析大文件。这是一个例子:

#!/usr/bin/perl
use warnings;
use strict;

use XML::LibXML::Reader;

my $reader = XML::LibXML::Reader->new(location => 'input.txt') or die;

while ($reader->read) {
    if ($reader->nodePath =~ m{/tagname$}                    # We are at <tagname> or </tagname>.
        and $reader->nodeType == XML_READER_TYPE_ELEMENT) {  # Only the start tag is interesting.
        print $reader->readInnerXml;
    }
}
于 2012-08-22T11:08:15.090 回答
3

为了从文件中读取较小的块,您可以将输入记录分隔符设置为结束标记:

BEGIN { $/ = "</tagname>"; }

这是一个例子:

代码:

perl -lnwe 'BEGIN { $/ = "</tagname>"; } print;'

输入:

<tagname>foo</tagname><tagname>bar</tagname><tagname>baz</tagname><tagname>baf</tagname>

输出:

<tagname>foo
<tagname>bar
<tagname>baz
<tagname>baf

您会注意到缺少结束标记,这是因为-l您使用的选项还包括一个chomp,它会删除输入记录分隔符。如果您不想要这种行为,只需删除该-l选项并在您的打印语句中插入一个换行符。

笔记:

我想说这有点像 hack,但它确实与您已经使用的匹配,即区分大小写、精确标签匹配。

您可以做的补偿是在其中使用您的正则表达式:

perl -lnwe 'BEGIN { $/ = "</tagname>"; } 
    while (/<tagname>(.*?)<\/tagname>/sg) { print $1 }' input.txt > output.txt

或者,可能,使用 XML 解析器来解析块。

如果其他人建议的 XML 解析器不适用于如此大的文件,这可能是一种读取较小数据块的方法,而不会冒险将标签切成两半。

于 2012-08-22T12:09:44.370 回答
1

您还可以使用 awk 来破坏一个大的单行文件。Sed 在尝试加载整行时会因内存不足而崩溃,但在 awk(如 perl 中)中,您可以定义要用作“换行符”的内容,从而绕过问题。

对于 perl,上面已经有一个示例,这里是 awk 示例:

cat big-one-line-file |  awk 'BEGIN { RS=">" } ; {print $0">"}'

请注意,在文件末尾,如果文件不以“>”结尾,则会显示一个额外的 >。您可以通过任何方式将其删除(例如清理后的 sed: sed '$ s/>$//')或调整脚本。

因为我也有这个问题,为了帮助别人,我会添加更多的例子来帮助测试。

您可以使用 dd 测试脚本以提取文件的一小部分并捕获更大的“记录分隔符”,例如作品或标签。例子:

dd if=big-one-line-file.xml bs=8192 count=10  | awk ' BEGIN { RS="<tag 123>" } ; NR>1 {print "<tag 123>"$0}  ; NR==1 {print $0}  ' 

提取 big-one-line-file.xml 的前 80kB 并在“”中破坏该文件。为了避免文件开头的额外(和错误)“”,请区别对待(即:不要触摸它)

使用 dd 选项skip={# of blocks to reach near the file size} 提取文件的末尾而不是顶部(尾部将失败,因为它总是只有一行)。我使用了 skip=100000000 并开始删除零,直到出现某些内容并调整块号。

于 2012-08-27T14:15:58.623 回答
0

我会对您的输入文件应用过滤器以引入换行符。也许在每个之后</tagname>BEGIN{undef $/}然后,您将能够摆脱perl命令并通过处理“合理”记录来避免内存问题。

于 2012-08-22T10:31:36.617 回答
0

目前尚不清楚您输入的文件是否是格式正确的 XML。您给出的示例不是 XML(没有根元素)。如果数据是 XML,您可以使用XML::Twigxml_grep附带的工具。这适用于任何大小的文件,前提是每个匹配的元素都可以放入内存中。xml_grep -r tagname --text_only mybig.xml

如果这太慢,您可能可以通过直接使用 XML::Parser 来获得一些速度,代码编写起来并不复杂。虽然不必编写它更容易;--)

于 2012-08-22T13:52:58.157 回答