【问题标题】:Insert 1000+ nodes and attributes with XMLStarlet - Running Slow使用 XMLStarlet 插入 1000 多个节点和属性 - 运行缓慢
【发布时间】:2018-04-24 09:03:38
【问题描述】:

这是一个效率问题,而不是故障排除问题。我有以下代码sn-p:

# The -R flag restores malformed XML
xmlstarlet -q fo -R <<<"$xml_content" | \
    # Delete xml_data
    xmlstarlet ed -d "$xml_data" | \
    # Delete index
    xmlstarlet ed -d "$xml_index" | \
    # Delete specific objects
    xmlstarlet ed -d "$xml_nodes/objects" | \
    # Append new node
    xmlstarlet ed -s "$xml_nodes" -t elem -n subnode -v "Hello World" | \
        # Add x attribute to node
        xmlstarlet ed -i "($xml_nodes)[last()]" -t attr -n x -v "0" | \
        # Add y attribute to node
        xmlstarlet ed -i "($xml_nodes)[last()]" -t attr -n y -v "0" | \
        # Add z attribute to node
        xmlstarlet ed -i "($xml_nodes)[last()]" -t attr -n z -v "1" \
            > "$output_file"
  • 变量$xml_content包含内容的xml树和
    使用cat 命令从大小为 472.6 MB 的文件中解析出的节点。

  • 变量$output_file顾名思义,包含路径 到输出文件。

  • 其余变量仅包含我要编辑的相应 XPath。

根据帮助提出此代码的简短article,它表明:

这有点低效,因为 xml 文件被解析和写入两次。

在我的例子中,它被解析和写入两次以上(最终在 loop 中超过 1000 次)。

因此,以上述脚本为例,该短片段的执行时间仅是 4 分 7 秒。

假设过多、重复且可能效率低下的管道以及文件大小是代码运行缓慢的原因,我最终插入/删除的子节点越多最终会导致它执行得更慢。

如果我通过重申自己或提出一个旧的且可能已经回答的主题可能听起来单调,我提前道歉,但是,我真的很想了解 xmlstarlet 如何详细处理大型 XML 文档。


更新

正如@Cyrus 在他之前的回答中所说:

这两个 xmlstarlets 应该可以完成这项工作:

xmlstarlet -q fo -R <<<"$xml_content" |\
  xmlstarlet ed \
    -d "$xml_data" \
    -d "$xml_index" \
    -d "$xml_nodes/objects" \
    -s "$xml_nodes" -t elem -n subnode -v "Hello World" \
    -i "($xml_nodes)[last()]" -t attr -n x -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n y -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n z -v "1" > "$output_file"

这产生了以下错误:

  • -:691.84: Attribute x redefined
  • -:691.84: Attribute z redefined
  • -:495981.9: xmlSAX2Characters: huge text node: out of memory
  • -:495981.9: Extra content at the end of the document

老实说,我不知道这些错误是如何产生的,因为我经常更改代码以测试各种场景和潜在的替代方案,但是,这就是我的诀窍:

xmlstarlet ed --omit-decl -L \
    -d "$xml_data" \
    -d "$xml_index" \
    -d "$xml_nodes/objects" \
    -s "$xml_nodes" -t elem -n subnode -v "Hello World" \
    "$temp_xml_file"

xmlstarlet ed --omit-decl -L \
    -i "($xml_nodes)[last()]" -t attr -n x -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n y -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n z -v "1" \
    "$temp_xml_file"

关于插入的实际data,这是我一开始的:

...
<node>
    <subnode>A</subnode>
    <subnode>B</subnode>
    <objects>1</objects>
    <objects>2</objects>
    <objects>3</objects>
    ...
</node>
...

执行上面的(拆分)代码给了我想要的:

...
<node>
    <subnode>A</subnode>
    <subnode>B</subnode>
    <subnode x="0" y="0" z="1">Hello World</subnode>
</node>
...

通过拆分它们,xmlstarlet 能够将attributes 插入到新创建的节点中,否则它将在--subnode 甚至创建之前将它们添加到所选Xpath 的last() 实例中。在某种程度上,这仍然是低效的,不过,现在代码运行不到一分钟。

以下代码,

xmlstarlet ed --omit-decl -L \
    -d "$xml_data" \
    -d "$xml_index" \
    -d "$xml_nodes/objects" \
    -s "$xml_nodes" -t elem -n subnode -v "Hello World" \
    -i "($xml_nodes)[last()]" -t attr -n x -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n y -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n z -v "1" \
    "$temp_xml_file"

但是,给我这个:

...
<node>
    <subnode>A</subnode>
    <subnode x="0" y="0" z="1">B</subnode>
    <subnode>Hello World</subnode>
</node>
...

通过将xmlstarlets 加入到post 中也由@Cyrus 回答的类似中,它以某种方式首先添加attributes,然后创建--subnode,其中innerTextHello World

  • 谁能解释为什么会发生这种奇怪的行为?

这是另一个reference,它声明“每个编辑操作都是按顺序执行的

上面的文章准确地解释了我正在寻找的东西,但我无法设法使其在一个 xmlstarlet ed \ 中全部工作。或者,我试过了:

  • ($xml_nodes)[last()] 替换为$xml_nodes[text() = 'Hello World']
  • 使用$prev(或$xstar:prev)作为-i 的参数,就像在这个answer 中一样。 [Examples]
  • temporary element name 技巧通过 -r 在添加 attr 后重命名临时节点

以上所有内容都插入--subnode,但新元素不带attributes

注意:我在 OS X El Capitan v 10.11.3 上运行 XMLStarlet 1.6.1


奖金

正如我在开头提到的那样,我希望使用 loop 类似的东西:

list="$(tr -d '\r' < $names)"

for name in $list; do
    xmlstarlet ed --omit-decl -L \
    -d "$xml_data" \
    -d "$xml_index" \
    -d "$xml_nodes/objects" \
    -s "$xml_nodes" -t elem -n subnode -v "$name" \
    -i "($xml_nodes)[last()]" -t attr -n x -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n y -v "0" \
    -i "($xml_nodes)[last()]" -t attr -n z -v "1" \
    "$temp_xml_file"
done

$list 包含一千多个不同的名称,需要添加它们各自的attributes。每个属性的--value 也可能因每个loop 而不同。鉴于上述模型:

  • 如果属性已正确添加到相应的节点,那么loop 的最快和最准确的版本是什么?

  • 在外部 txt 文件中创建节点列表,然后将这些 xml 元素(在 txt 文件中)添加到另一个 XML 文件中会更快吗?如果是,如何?也许是sedgrep

关于最后一个问题,我指的是this。应该添加来自 txt 的 xml 的节点必须是特定的,例如至少可以通过 XPath 选择,因为我只想编辑某些节点。

注意:上面的模型只是一个例子。实际的loop 将为每个loop 添加26 个--subnodes,为每个--subnode 添加3 或4 个attr。这就是为什么 xmlstarlet 正确添加 attr 而不是其他元素很重要的原因。它们必须按顺序添加。

【问题讨论】:

  • 我用示例输入/输出更新了我的问题,就像你建议的@Cyrus

标签: xml bash shell unix xmlstarlet


【解决方案1】:

为什么不使用并行(或 sem),以便您可以在机器上可用的内核数量上并行化作业? 我使用的代码是解析一个包含 2 个变量的数组,我将其导出到本地只是为了确保进程是隔离的。

for array in "${listofarrays[@]}"; do
    local var1;local var2
    IFS=, read var1 var2 <<< $array
    sem -j +0
    <code goes here>
done
sem --wait

【讨论】:

  • 感谢@user1747036,但我也想知道如何正确添加attr。你有什么建议吗?我实际上非常喜欢你阅读数组的想法。你能稍微扩展一下输入和输出的样子吗?
  • 顺便说一句,我看到了这个post,它说文件被读入内存。并行化 xmlstarlets 不会占用我所有的内存吗?只需使用 xmlstarlet 导入整个节点列表(包括 attr),就像在这个 answer 中一样,听起来很合适,但是我如何选择正确的节点才能将元素添加为子节点?
  • 类似:$ sed '/&lt;\/Students&gt;/{ r add.txt a \&lt;/Students&gt; d }' file,但将&lt;/Students&gt; 替换为正确的(并且可能是唯一的)节点(使用 XPath?)
【解决方案2】:

unbuffer 可能会有所帮助

expect 包中取消缓冲

从两个命令 a、z 构建管道

unbuffer a | z

从三个(或更多)命令 a、b、z 构建管道
在管道内添加 -p 选项

unbuffer a | unbuffer -p b | z

来源:盗自stackexchange

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-24
    • 2021-05-03
    • 2019-07-30
    • 2022-11-11
    • 2016-01-14
    • 1970-01-01
    • 2010-10-06
    相关资源
    最近更新 更多