1

我有一个包含此 XML 数据的文件:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>
    <tag1>some text</tag1>
    <tag2><![CDATA[http://url1.com]]></tag2>
    <tag3 />
    <tag4>not empty node</tag4>
  </item>
  <item>
    <tag1>some other text</tag1>
    <tag2><![CDATA[http://www.url.com]]></tag2>
    <tag3 />
    <tag4 />
  </item>
</root>

(里面还有很多 XML)

我正在尝试编写一个 Bash 脚本来删除一些 XML。即,我想删除每个<item>具有空子元素的<tag4>元素。

因此,我想先查找<item>然后查找<tag4/>,然后</item>将其分组并替换为X字符。

我什至还没有开始分组,我一直坚持在多行上做一个正则表达式。

在 Mac OS X 上运行

这就是我得到的:

 perl -pn -e "s/<item>[\s\S]*<tag4 \/>/X/g" $XML_FILENAME > new_folder/$XML_FILENAME

如果我删除[\s\S]*( 这意味着任何空格字符或任何字符,我可以替换<item>标签,但我无法进入下一个标签或下一行。

(我也尝试过echo//sed陷入类似的位置)

4

5 回答 5

3

最好为此使用一个实际的 XML 解析器(例如)并使用XPath表达式XML::LibXML选择空<tag4>节点:

#!/usr/bin/env perl

use strict;
use warnings;
use XML::LibXML;

my $xml = XML::LibXML->new->parse_file('/path/to/input.xml');

$_->unbindNode for $xml->findnodes('//item[not(tag4/text())]');

print $xml->toString;

如果要将修改后的 XML 直接保存到文件中,请替换该行

print $xml->toString;

$xml->toFile('/path/to/output.xml');
于 2013-06-26T15:54:38.780 回答
2

尝试这个:

s/<item>(?>[^<]++|<(?!tag4))*<tag4 \/>(?>[^<]++|<(?!\/item>))*<\/item>/X/g

这种模式避免了换行问题,因为它不使用点。

解释

的细节(?>[^<]++|<(?!tag4))*

(?>                # open an atomic group
      [^<]++       # all that is not a < one or more times (possessive)
    |              # OR
      <(?!tag4)    # a < not followed by tag4
)*                 # close the atomic group, repeat zero or more times

使用这个技巧,我确信接下来是<tag4(或字符串的结尾)

我使用原子组 (?>..)所有格量词 来获得更多性能,但您可以用普通组和贪婪量词++替换它们(?:..)+

通知

或者你可以使用一个惰性量词替换[\s\S]*[\s\S]*?

请注意,使用 perl 您可以使用 dotall 模式而不是[\s\S]添加 s 修饰符:

 (?s).*          # the dot matches newlines
 (?-s).*         # the dot doesn't match newlines (default behavior)
于 2013-06-26T15:33:27.757 回答
2

使用正则表达式处理 XML 是不切实际的。您应该使用适当的 Perl 模块。

这个简短的程序用于XML::Twig处理名称作为命令行参数传递的文件。它将修改后的 XML 发送到STDOUT.

use utf8;
use strict;
use warnings;

use XML::Twig;

my $twig= XML::Twig->new(pretty_print => 'indented');
$twig->parsefile($ARGV[0]);

for my $twig ($twig->findnodes('/root/item')) {
  $twig->delete unless $twig->findvalue('tag4') =~ /\S/;
}

$twig->print;

输出

<?xml version="1.0" encoding="utf-8"?>
<root>
  <item>
    <tag1>some text</tag1>
    <tag2><![CDATA[http://url1.com]]></tag2>
    <tag3/>
    <tag4>not empty node</tag4>
  </item>
</root>
于 2013-06-26T15:53:43.987 回答
0

这可行,但需要做一些工作:

 perl -00 -ne 's/<item>.*<\/tag4>.*<\/item>/X/gs;print "$_\n";' test.xml 
于 2013-06-26T16:52:18.197 回答
0

一种方法GNU awk

awk '
BEGIN {
    ORS=""
    RS="<[/]?item>"
    f1="<item>"
    f2="<\/item>"
}
!/<tag4 \/>/ && NF { 
    print ($0~/tag/)?f1 $0 f2:$0
}' xmlfile
于 2013-06-26T15:56:24.193 回答