如何使用sed一次性提取标记(token)间的多个子串?
用sed提取XML中指定标签内容的解决方案
我懂你之前用sed提取token间子串时只能拿到最后一个结果的烦恼,现在换成单行XML输入,要提取每个<message>里的<receiver>、<sender>、<text>内容并按要求输出,咱们一步步来解决这个问题。
问题回顾
你的真实输入XML:
<archive><message id="0"><receiver>apr</receiver><sender>gtr</sender><text>52333</text><sendTime>554</sendTime><deliveryTime>765</deliveryTime></message><message id="0"><receiver>apr</receiver><sender>gtr</sender><text>4332</text><sendTime>764</sendTime><deliveryTime>922</deliveryTime></message></archive>期望输出:
apr gtr 52333 apr gtr 4332
为什么原来的sed命令失效?
你之前的命令sed "s/^.* \?token\(.* \)token.* \?/\1/"只能拿到最后一个子串,核心原因有两个:
- sed的
.*是贪婪匹配,会从开头一直匹配到最后一个token,所以只能捕获到最后一对token之间的内容; - 命令只执行了一次替换,没有循环处理所有匹配的
token对,自然无法提取多个结果。
针对XML的sed解决方案
这里给出专门适配你需求的sed命令,同时逐行解释每个部分的作用:
sed -n ' # 定义循环标签,用于重复处理所有匹配的标签 :loop # 提取<receiver>标签内的内容,替换为内容+空格,替换成功则回到loop继续处理 s/<receiver>\([^<]*\)<\/receiver>/\1 /; t loop # 同理处理<sender>标签 s/<sender>\([^<]*\)<\/sender>/\1 /; t loop # 同理处理<text>标签 s/<text>\([^<]*\)<\/text>/\1 /; t loop # 过滤掉所有不需要的内容,只保留我们提取的内容块 s/.*\(\([^ ]* [^ ]* [^ ]* \)*\).*/\1/ # 去掉最后多余的空格 s/ $// # 输出最终处理后的内容 p ' inputFile > outputFile
命令细节解释:
-n:关闭sed默认的每行输出,只在我们用p指令时才输出结果;:loop:自定义一个循环标记,用来重复执行替换操作,直到所有目标标签都被处理;s/<receiver>\([^<]*\)<\/receiver>/\1 /:用[^<]*精准匹配<receiver>标签内的文本(因为标签闭合符是<,所以匹配到<就停止),把整个标签替换成提取的文本加空格;t loop表示如果本次替换成功,就跳回loop标记继续处理,确保所有<receiver>标签都被处理;s/.*\(\([^ ]* [^ ]* [^ ]* \)*\).*/\1/:把XML中其他无关的标签、文本全部过滤掉,只保留我们提取的“接收者 发送者 文本”格式的内容块;s/ $//:移除最后一个多余的空格,让输出格式更整洁;p:输出最终整理好的结果到outputFile。
验证结果
执行上述命令后,outputFile里的内容就会和你期望的一致:
apr gtr 52333 apr gtr 4332
内容的提问来源于stack exchange,提问作者Mariusz Bakun
相关产品推荐
相关产品推荐

