我正在使用以下内容提取XML标记之间的内容: -
perl -lne 'BEGIN{undef $/} while (/<tagname>(.*?)<\/tagname>/sg){print $1}' input.txt > output.txt
不幸的是我收到out of memory
个问题,我知道我可以分割文件并处理每个然后concat但是我想知道是否有另一种方式,无论是修改上面的内容还是使用类似awk或者SED?
input.txt
文件大小介于17GB和70GB之间。
编辑:
输入文件可以是任何XML文件,需要注意的一点是它不包含换行符,例如: -
<body><a></a><b></b><c></c></body><foo></foo><bar><z></z></bar>
答案 0 :(得分:3)
这个单行程序将整个文件作为一个巨大的“行”读入内存。当然你会遇到17GB以上的内存问题!逐行阅读和处理文件或使用read
来获取合适大小的块。
在这种情况下,搜索<tagname>
,记下它的位置并从那里开始搜索结束标记。如果你没有找到它,将当前行/块填充到缓冲区并重复,直到你在文件中的其他行中找到它。找到后,打印出此缓冲区并清空它。重复直到文件结束。
请注意,如果您使用任意大小的块,则必须通过从块末端切割不完整的标记并将其填充到“处理”缓冲区来考虑边界分割标记的可能性。
答案 1 :(得分:3)
使用像XML::LibXML::Reader之类的拉解析器可以解析大文件。这是一个例子:
#!/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;
}
}
答案 2 :(得分: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
选项并在print语句中插入换行符。
注意:强>
我会说这有点像黑客,但它确实与你已经使用的相匹配,即匹配区分大小写的确切标签。
你可以做些什么来补偿就是在你的内部使用你的正则表达式:
perl -lnwe 'BEGIN { $/ = "</tagname>"; }
while (/<tagname>(.*?)<\/tagname>/sg) { print $1 }' input.txt > output.txt
或者,可能使用XML解析器来解析块。
如果其他人建议的XML解析器不能用于这么大的文件,那么这可以是一种读取较小数据块的方法,而不会有将标签切成两半的风险。
答案 3 :(得分:1)
你也可以使用awk打破一个大的一行文件。当它试图加载整行时,Sed会因为内存不足而破坏,但是在awk中(如在perl中),您可以定义要用作“换行符”的内容,绕过问题。
对于perl,你上面已经有一个例子,这里是awk:
cat big-one-line-file | awk 'BEGIN { RS=">" } ; {print $0">"}'
请注意,在文件末尾,还有一个&gt;将显示文件是否以“&gt;”结尾。您可以通过任何方式将其删除(例如发布后清除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并开始删除零,直到出现并调整块编号。
答案 4 :(得分:0)
我会在输入文件中应用过滤器来引入换行符。也许在每个</tagname>
之后?然后,您将能够在BEGIN{undef $/}
命令中删除perl
通过处理“合理”记录来避免记忆问题。
答案 5 :(得分:0)
目前尚不清楚您输入的文件是否是格式良好的XML。您提供的示例不是XML(没有根元素)。如果数据是XML,则可以使用xml_grep
XML::Twig附带的工具。 xml_grep -r tagname --text_only mybig.xml
这将适用于任何大小的文件,前提是每个匹配的元素都可以放在内存中。
如果速度太慢,你可以通过直接使用XML :: Parser获得一些速度,编写代码并不是很复杂。不过不必写它会更容易; - )