如何在Linux Bash中移除含指定子串的可变长度<game>XML块
问题描述
我有一个记录多款游戏元数据的XML文件,内容示例如下:
<?xml version="1.0"?> <gameList> <game> <path>./Besiege.desktop</path> <name>Besiege</name> <desc>Long description of game</desc> <releasedate>20150128T000000</releasedate> <developer>Spiderling Studios</developer> <publisher>Spiderling Studios</publisher> <genre>Strategy</genre> <players>1</players> </game> <!-- 其他大量条目 --> <game> <path>./67000.The Polynomial.txt</path> <name>The Polynomial - Space of the music</name> <desc>Long description of game</desc> <releasedate>20101015T000000</releasedate> <developer>Dmytry Lavrov</developer> <publisher>Dmitriy Uvarov</publisher> <genre>Shooter, Music</genre> <players>1</players> <favorite>true</favorite> </game> <!-- 更多条目 --> </gameList>
需要移除所有<path>标签包含.desktop子串的整个<game>块(从<game>到</game>的所有内容),但<game>块的字段数量不固定,<desc>内的描述也可能有多段,没法用固定行数的方法处理。
解决方案
方法1:用awk快速处理(适合非严格XML场景)
awk可以按块收集内容,判断后输出:
awk ' /<game>/ { in_game=1; block=""; } in_game { block = block $0 "\n"; } /<\/game>/ { in_game=0; if (block !~ /\.desktop/) { printf "%s", block; } } !in_game { print; } ' input.xml > output.xml
逻辑说明:
- 碰到
<game>就标记进入游戏块,开始收集块内所有内容 - 到
</game>时停止收集,检查块里有没有.desktop,没有就输出整个块 - 不在游戏块里的内容(比如XML头、
<gameList>标签)直接原样输出
方法2:用sed处理(依赖XML格式规范)
sed通过保持空间和分支功能处理可变长度块:
sed '/<game>/,/<\/game>/ { /<game>/ {h;d;} /<\/game>/ { x; /\.desktop/!{g;p;} d; } H;d; }' input.xml > output.xml
逻辑说明:
- 先匹配
<game>到</game>的整个范围 - 遇到
<game>时,把该行存到保持空间,然后删除当前行 - 块内其他行都追加到保持空间
- 碰到
</game>时,交换保持空间和模式空间,检查是否含.desktop,不含就输出整个块 - 最后清理临时内容
方法3:用XML专用工具xmlstarlet(最可靠,推荐)
正则处理XML容易踩坑(比如标签换行、嵌套异常),用专门的XML工具更稳妥:
先安装xmlstarlet:
# Debian/Ubuntu sudo apt install xmlstarlet # macOS brew install xmlstarlet
然后执行删除命令:
xmlstarlet ed -d "/gameList/game[contains(path/text(), '.desktop')]" input.xml > output.xml
逻辑说明:
ed表示编辑XML文件-d指定要删除节点- XPath表达式
/gameList/game[contains(path/text(), '.desktop')]精准定位所有<path>含.desktop的<game>节点,直接删除
内容的提问来源于stack exchange,提问作者Calibre
相关产品推荐
相关产品推荐

