使用 sed 从类似 XML 的文件中删除特定文本
Posted
技术标签:
【中文标题】使用 sed 从类似 XML 的文件中删除特定文本【英文标题】:Remove specific text from an XML like file using sed 【发布时间】:2021-09-19 07:15:43 【问题描述】:我有以下文件(这是一个 JUnit 报告文件),我需要从中删除 system-out
和 system-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
以包含以前的新行(我结合了几个SO建议来拥有它和我真的不知道如何使用多个sed -N
标志):
tr "\n" "\f" < "$f" |
sed 's/\(<system-err>\)\(.*\)\(<\/system-err>\)/\1\3/' |
sed 's/\(<system-out>\)\(.*\)\(<\/system-out>\)/\1\3/' |
tr "\f" "\n" > $(basename "$f")-out.xml
这样做的问题是 sed 是贪婪的,例如将从第一个系统错误删除到最后一个,留下未关闭的元素。
我尝试了多种方法,还使用sed -E 's/<system-out><![(.*)]><\/system-out>//g'
模式来匹配system-*
文本之间的任何内容,但它并没有真正起作用。
我不是 sed 或 regexp 专家,所以请多多包涵 :)。我的限制是需要使用 sed(在 bash 脚本中)。
有人可以告诉我如何实现删除。
提前谢谢你!
【问题讨论】:
Don't Parse XML/HTML With Regex. 我建议使用 XML/HTML 解析器 (xmlstarlet, xmllint ...)。 【参考方案1】:与xidel:
$ 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>
【讨论】:
【参考方案2】: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>
【讨论】:
【参考方案3】:仅作记录,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
也会抱怨大文件。
真心希望这可以帮助其他在这种情况下苦苦挣扎的人。
【讨论】:
【参考方案4】:使用 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
【讨论】:
这个答案很棒,很好而且简短(成功了)。但是,虽然我知道将 sed 用于 XML/XHTML 之类的解析并不是最好的主意(至少可以这么说),但有没有办法使用 sed 呢? (我知道这可能被认为是“矫枉过正”) @avostache sed 使用 xml 并不过分;这更像是用螺丝刀敲钉子。 虽然我想要一个与 sed 相关的答案,但这个 xmlstarlet 非常完美,所以我会将其标记为已接受(此外,我知道这是处理 XML 文件处理的推荐做法)。谢谢你的帮助!以上是关于使用 sed 从类似 XML 的文件中删除特定文本的主要内容,如果未能解决你的问题,请参考以下文章
从文本文件中删除 Unicode 字符 - sed ,其他 Bash/shell 方法