使用 sed 从 XML 类文件中删除特定文本

Remove specific text from an XML like file using sed

我有以下文件(这是一个 JUnit 报告文件),我需要从中删除 system-outsystem-err 节点及其内容,同时保留其他节点结构(元素和值)。

我的文件具有以下类型的结构和内容(请注意 system-* 元素可以包含多行内容和 html 标签):

<testsuite name="someTest" tests="1" skipped="0" failures="0" errors="0">
  <properties/>
  <testcase name="someMethod" classname="classA" time="0.096">
    <system-out><![CDATA[foo <li></li> bar]]></system-out>
    <system-err><![CDATA[[one] INFO two
three four 
five]]></system-err>
  </testcase>
  <system-out><![CDATA[]]></system-out>
  <system-err><![CDATA[]]></system-err>
</testsuite>

想要的结果是

<testsuite name="someTest" tests="1" skipped="0" failures="0" errors="0">
  <properties/>
  <testcase name="someMethod" classname="classA" time="0.096">
  </testcase>
</testsuite>

我已经尝试了 sed 模式的多种变体,下面的不是很好但部分有效。当前的方法是使用 tr 将新行替换为一些外来字符,然后在单行文本上应用 sed,然后重复使用 tr 来包含之前的新行(我结合了几个所以建议拥有它,我真的不知道如何使用多个 sed -N 标志):

tr "\n" "\f" < "$f" |
sed 's/\(<system-err>\)\(.*\)\(<\/system-err>\)//' |
sed 's/\(<system-out>\)\(.*\)\(<\/system-out>\)//' |
tr "\f" "\n" > $(basename "$f")-out.xml

这个问题是 sed 是贪婪的,例如将从第一个系统错误删除到最后一个,留下未关闭的元素。 我尝试了多种方法,也尝试使用 sed -E 's/<system-out><![(.*)]><\/system-out>//g' 模式来匹配 system-* 文本之间的任何内容,但它并没有真正起作用。

我不是 sed 或 regexp 专家,所以请宽恕:)。我的限制是需要使用 sed(在 bash 脚本中)。

有人能告诉我如何实现删除 .

提前致谢!

使用 xmlstarlet:

xmlstarlet edit --omit-decl --delete '//system-out' --delete '//system-err' file.xml

输出:

<testsuite name="someTest" tests="1" skipped="0" failures="0" errors="0">
  <properties/>
  <testcase name="someMethod" classname="classA" time="0.096"/>
</testsuite>

参见:xmlstarlet edit --help

:

$ xidel -s input.xml -e '
  x:replace-nodes(/,(//system-out,//system-err),())
' --output-node-format=xml --output-node-indent
<testsuite name="someTest" tests="1" skipped="0" failures="0" errors="0">
  <properties/>
  <testcase name="someMethod" classname="classA" time="0.096">
  </testcase>
</testsuite>

郑重声明,xmlstarlet 不适用于大文件(即,对于 30+ MB 大小的文件,它会引发“巨大的输入查找”错误)。但这对于我最初问题中的小用例来说非常棒,所以 Cyrus 的回答成功了。

如果有人需要处理更大文件的东西,如前所述(我个人也需要可扩展的东西),我找到了一个 Python 相关的直接解决方案(所以这里也没有 sed) :

import xml.etree.ElementTree as ET

file = "myJunitReport.xml"    
tree = ET.parse(file)
root = tree.getroot()

# remove top level system-out/system-err
for elem in root.findall('system-out'):
    root.remove(elem)
for elem in root.findall('system-err'):
    root.remove(elem)

# remove testcase related system-out/system-err
for child in root.findall("testcase"):
    for profile in child.findall(".//system-out"):
        child.remove(profile)
    for profile in child.findall(".//system-err"):
        child.remove(profile)

tree.write(file)

一个重要的部分是我使用 Python 的默认值 XML ElementTree API。其他解决方案,如 lxml.etree 也会抱怨大文件。

真心希望这能帮助那些在这种情况下苦苦挣扎的人。

sed.

警告:如果文件结构略有不同,很有可能无法运行。

sed -e '\|<system-out>.*</system-out>|d' \
    -e '\|<system-err>.*</system-err>|d' \
    -e '\|<system-err>|,\|</system-err>|d' file.xml

我从//切换到\||

输出:

<testsuite name="someTest" tests="1" skipped="0" failures="0" errors="0">
  <properties/>
  <testcase name="someMethod" classname="classA" time="0.096">
  </testcase>
</testsuite>