【问题标题】:How to split a huge xml-file to smaller files after nth occurrence of certain tag?如何在某个标签第 n 次出现后将一个巨大的 xml 文件拆分为较小的文件?
【发布时间】:2017-08-16 14:02:11
【问题描述】:

我有一个 30 GB 的 xml 文件,我想将其拆分为更小的文件。

文件中的数据是这样的:

<film>.....</film>
.
.
.
.
.
.
<film>.....</film>

我可以使用“split -l”,但问题是某些电影元素包含带有换行符的文本数据。所以一个电影元素可能不止一条线。

我想做的是拆分它,以便每个新的较小文件包含例如 3000 个电影元素。所以它应该在每 3000 个电影标签后拆分它......

我正在使用 Mac OS X,我想要一个 awk 解决方案。

我尝试使用这个split file on Nth occurrence of delimiter,但没有成功...在结束电影标签后没有拆分文件...

【问题讨论】:

  • 我想你的 awk 不是 gnu,对吧?
  • 是的,这是 awk 不是 gawk... :)
  • gawk IS 是一个 awk,就像每个名为 awk 的工具一样,它也是一些变体(awk、nawk、tawk、mawk、gawk、/usr/xpg4/bin/awk、等等......)所以说“它是awk不是gawk”并没有真正的意义。可以安装gawk吗?没有它,你会错过很多非常有用的功能。 edit您的问题包含简洁、可测试的样本输入和预期输出,因此我们可以开始为您提供帮助。将所有这些...s 替换为真正具有代表性的样本值,如果您必须处理某些位置的换行符,请将它们包含在您的样本中。

标签: xml macos awk


【解决方案1】:

流式传输 XSLT 3.0 的作业:

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:mode streamable="true" on-no-match="shallow-copy"/>    
  <xsl:template match="/*">
    <xsl:for-each-group select="*" group-adjacent="(position()-1) idiv 3000">
      <xsl:result-document href="chunk{position()}.xml">
        <xsl:copy>
          <xsl:copy-of select="."/>
        </xsl:copy>
      </xsl:result-document>
    </xsl:for-each-group>
  </xsl:template>
</xsl:transform>

这将比 awk 解决方案更健壮,因为它实际上解析 XML,因此它保证格式正确的输入和格式正确的输出。当您处理 30Gb 时,您无法手动检查输出,因此如果您未能预料到输入中可能出现的所有内容(例如标题中带有“电影”的电影),则存在未检测到垃圾的严重危险。因此,在标记结构上正常工作要安全得多。

另一件事是,如果您的输入是格式良好的 XML,它在 &lt;film&gt; 元素周围有一个包装器元素,如果输出要作为 XML 处理,则需要一个类似的包装器元素。 XSLT 解决方案免费处理这个问题。

您可能已经注意到,此样式表可以将任何 xml 文件拆分为块,当然块大小可以很容易地作为参数提供。

【讨论】:

  • 是您发布的脚本吗?如果是,执行该脚本的工具的名称是什么?OP 如何根据他的输入在 OSX 上执行它?如果它不是脚本 - 它是什么?我做了谷歌“流式 XSLT 3.0”,但不想涉足第一次点击,saxonica.com/html/documentation/sourcedocs/streaming/…
  • @EdMorton 我同意答案将从一个工作示例中受益。我发现了这个:saxonica.com/documentation9.5/using-xsl/commandline.html 但没有尝试过。一个小的自定义 Java 程序也可以是一种选择。对于任何专业的重复用例,我绝对推荐使用 XSLT。
  • 如果回答“我用什么命令来运行它?”是“创建自定义 Java 程序”,那么我不在乎它有多健壮...... :-)。不是有什么工具可以用吗?
  • 我有点惊讶,任何人都应该在处理 XML 时至少对 XSLT 有一些了解——这有点像找一个没有螺丝刀的电工。然而,与任何工具一样,即使您运行的是为您编写的程序,也有一点学习曲线。目前有两种可用的流式 XSLT 3.0 处理器,Saxon-EE (Java) 和 Exselt (.NET),您需要安装其中一个的评估副本。对于撒克逊人来说,它只是在命令行上的java -jar saxon9ee.jar -s:source.xml -xsl:style.xsl -o:outdir
  • @MichaelKay 到目前为止(听起来)还没有开源实现这一事实极大地限制了它的实用性。我没有意识到这一点。
【解决方案2】:

您正在寻找的可能是这样的:

awk '{ gsub(/@/,"@A"); gsub(/}/,"@B"); gsub(/<\/film>\n?/,"}") } 1' file |
awk -v RS='}' -v ORS='</film>' '
    (NR%3000)==1 { close(out); out="out"++cnt }
    { gsub(/@B/,"}"); gsub(/@A/,"@"); print > out }
'

但没有样本输入/输出,这是一个猜测,当然,未经测试。

【讨论】:

  • @ed morton,您的脚本运行良好。拆分后我需要在文件中添加一些文本。知道如何处理上面的脚本吗?
  • 用自己简洁、可测试的样本输入和预期输出提出一个新问题。
【解决方案3】:

当 Ed Morton 发布 awk 解决方案时,它通常是针对像我这样的低级别用户的小教程...

但无论如何,自从我过去一个半小时一直在做这个练习,我想冒险发布这个解决方案,这是一个转换by the link you already found

$ awk '$0 ~/<film.*>/{++delim} {file = sprintf("chunk%s", int(delim/7)); print >file; }' file4 

测试:
我使用了一个小的 bash 循环来创建一个包含 50 条记录的小电影文件,并将这些电影分成 7 份进行测试:

$ for ((i=1;i<50;i++));do echo -e "<film$i>..............</film$i>" >>file4;done
$ head file4
<film1>..............</film1>
<film2>..............</film2>
<film3>..............</film3>
<film4>..............</film4>
<film5>..............</film5>
<film6>..............</film6>
<film7>..............</film7>
<film8>..............</film8>
<film9>..............</film9>
<film10>..............</film10>

$ awk '$0 ~/<film.*>/{++delim} {file = sprintf("chunk%s", int(delim/7)); print >file; }' file4 
$ cat chunk0
<film1>..............</film1>
<film2>..............</film2>
<film3>..............</film3>
<film4>..............</film4>
<film5>..............</film5>
<film6>..............</film6>

每部电影都有一些换行符的另一个测试:

$ for ((i=1;i<50;i++));do echo -e "<film$i>...\n...\n...\n.....</film$i>" >>file4;done
$ head -n20 file4
<film1>...
...
...
.....</film1>
<film2>...
...
...
.....</film2>
<film3>...
...
...
.....</film3>
<film4>...
...
...
.....</film4>
<film5>...
...
...
.....</film5>


$ awk '$0 ~/<film.*>/{++delim} {file = sprintf("chunk%s", int(delim/7)); print >file; }' file4 

$ ls chunk*
chunk0  chunk1  chunk2  chunk3  chunk4  chunk5  chunk6  chunk7

$ cat chunk1
<film7>...
...
...
.....</film7>
<film8>...
...
...
.....</film8>
<film9>...
...
...
.....</film9>
<film10>...
...
...
.....</film10>
<film11>...
...
...
.....</film11>
<film12>...
...
...
.....</film12>
<film13>...
...
...
.....</film13>

嗯,在这两种情况下似乎都可以正常工作。请注意,在此配置中,输入文件按 7 部电影进行拆分,而不是按 7 行。您可以将此数字更改为任何值。

【讨论】:

  • 不幸的是,这似乎不适用于我......我认为它与原始代码有同样的问题,它没有找到正确的标签......它只是制作一个文件这与原来的相同。不过还是谢谢:)
  • @jubakala 感谢您的反馈。只是为了理解一些事情:我模拟您的文件的方式以及我在 Debian 中进行的测试是否与您的实际情况相匹配,或者我弄错了?
  • 在实际情况下,电影元素没有像 等那样编号...它们只是 ...但我不认为那是问题,因为我在测试时更改了它。
  • @jubakala 我不知道为什么这个方法会失败。我还在非 gnu awk (FreeBSD) 上测试了这段代码,似乎工作正常。
  • @jubakala 顺便说一句,我使用相同的正则表达式(awk '$0 ~/&lt;film.*&gt;)对仅包含 (无数字)的文件进行了第二次测试,并且工作正常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-21
  • 1970-01-01
  • 1970-01-01
  • 2019-07-14
  • 1970-01-01
相关资源
最近更新 更多