sed 2 个标签/模式之间的前 n 次出现结果集

sed 2 个标签/模式之间的前 n 次出现结果集

我有一个很大的 XML 文件,我得到了 2 个标签之间出现的所有事件:

这是我所做的:

sed -n '/<tag>/,/<\/tag>/p' file.xml

我需要过滤以仅获取前 N 个出现的情况。我尝试过 l param 但还不够:(

那么有人知道如何从所有结果集中获取 N 个匹配的事件吗?

例如。这里是 xml 文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
</root>

 sed -n '/<tag>/,/<\/tag>/p' file.xml 

返回所有元素。

所以目标是过滤以获得前 n 个匹配模式(元素是多行)如果 n = 2 那么结果 =:

<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>

答案1

尝试:

xmllint --xpath '//tag[position()<=2]' file.xml

或者:

xmlstarlet sel -t -c '//tag[position()<=2]' file.xml

或者:

xmlstarlet sel -t -m '//tag[position()<=2]' -c . -n file.xml

如果你想sed只用你可以这样做:

sed -n '
  1{x;s/^/../;x;}; # initialise counter with two tokens
  /<tag>/,/<\/tag>/ {
    p; /<\/tag>/{
      x;s/.//;/./!q;x; # remove a token and quit if hold space empty
    }
  }' file.xml

也就是说,使用保留空间作为要显示的剩余部分的计数器(使用点字符)。

答案2

您确实应该为此使用解析器,但是,正如您所知,sed -n '/<tag>/,/<\/tag>/p' file.xml它会获取所有元素,因为您p将它们全部打印出来。该命令的工作原理是对输入中包含 的行<tag>和包含 的下一行之间的所有行进行寻址</tag>。因为这几乎构成了你所有的台词,所以仅仅p打印它们并不会显示出太大的差异。像下面这样的东西可能更接近目标:

sed -n '\|<tag>|{:n
    \|</tag>|!{N;bn}
    y|\n| |;p
}'

它对<tag>行进行寻址并检查它们的</tag>.如果它们不包含结束字符串,则会拉入另一行 - 并且会重复执行此操作,直到模式空间包含<tag>.*</tag>[^\n]*$.

然后我将\n模式空间中的所有 ewline 字符转换为空格。

又是这样:

sed -n '\|<tag>|{:n;\|</tag>|!{N;bn};y|\n| |;p}' <<\DATA
<?xml version="1.0" encoding="UTF-8"?>
<root>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
</root>
DATA

输出:

<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>
<tag>  <t1>john</t1>  <t2>john</t2>  <t3>john</t3> </tag>

现在你可能会这样做:

sed -n '\|<tag>|{:n
    \|</tag>|!{N;bn}
    y|\n| |;p
}' ./file | 
sed 's|> |>\n|g;2q'

...这让我:

<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>
<tag>
 <t1>john</t1>
 <t2>john</t2>
 <t3>john</t3>
</tag>

答案3

我想这就是你想要的

sed -n '/<tag>/,/<\/tag>/p' file.xml | head -10

尝试以下命令获取以<tag>,开头的前两行

$ sed -n '/^<tag>/p' file.xml | head -2
<tag><t1>john</t1></tag>
<tag><t1>john</t1></tag>

答案4

据我所知,sed匹配总是贪婪的,/<tag>/,/<\/tag>/即将从第一个实例<tag>到最后一个实例进行匹配<\tag>- 包括之间的任何其他 XML 对象。

如果您的版本awk支持多字符记录分隔符,您也许可以执行类似的操作

awk -v n=2 'BEGIN{RS="</tag>\n";ORS=RS} NR<=n'

但真正更强大的解决方案是使用专用的 XML 解析器 - 例如使用 python 的非常简单的实现minidom

#!/usr/bin/python

from xml.dom import minidom

xmldoc = minidom.parse('file.xml')
taglist = xmldoc.getElementsByTagName('tag')
for i in range(2) :
        print taglist[i].toxml()

相关内容