在 XML 文件中查找并增加数字

Posted

技术标签:

【中文标题】在 XML 文件中查找并增加数字【英文标题】:Find and Increment a Number in an XML File 【发布时间】:2020-08-11 12:38:55 【问题描述】:

我正在尝试在XML 文件中搜索字符串,将紧随其后的数字增加1,然后将更改保存回同一个文件。这个字符串只有一个实例。

我的文件如下所示:

        <attribute>
                <name>test</name>
                <type>java.lang.String</type>
                <value>node1-3</value>
        </attribute>

我正在尝试更改 3(在 node1- 之后)并在每次运行命令时将其递增 1。我尝试了以下 sed,将该行分成4 块,并用4 块替换它,加上一个增量。不幸的是,它似乎没有做任何事情:

 sed -i -r -e 's/(.*)(\node1-)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/g' filepath

我也试过awk,这似乎让我在某个地方,但我不知道如何将后半部分重新添加到 (

awk 'FS=OFS="-" /node1/$2+=11' filepath

最后,我尝试了 perl,但它增加了错误的数字,从 node1node2,而不是在破折号之后:

perl -i -pe '/node1-/ && s/(\d+)(.*)/$1+1 . $2/e' filepath

我是这些命令的新手,对我的正则表达式不太熟悉。我试图让这个命令工作,这样我就可以在我正在编写的 bash 脚本中使用它。最好的方法是什么?哪个命令比另一个有优势?我想要一个 1 行命令来简化以后的事情。

【问题讨论】:

【参考方案1】:

你能硬编码节点行的最后一部分吗?

$ awk 'FS=OFS="-" /node1/$2+=1; print $1 "-" $2 "</value>" $0 !~ /node1/ print' file
  <attribute>
          <name>test</name>
          <type>java.lang.String</type>
          <value>node1-4</value>
  </attribute>

【讨论】:

这很好用! $0 !~ /node1/ 位的用途是什么?另外,我怎样才能把它写回文件中?目前它只打印。 sed/perl 中的 -i 是否有等价物? 你可以简化一点'BEGINFS=OFS="-" /node1/$2=(++$2)"&lt;/value&gt;"1',两者都使用解析技巧来获取数字。 @tarekeldarwiche $0 !~ /node1/ 位使其也打印与/node1/ 不匹配的行。要将其写入文件,只需将输出通过管道传输到带有command &gt; output-file 的文件。不过,从我刚才的测试来看,输出文件似乎必须是不同的文件,之后您需要将其重命名为原始名称 @karakfa 有效,但我不明白没有node1 是如何打印行的?我猜在右大括号之后与1 有什么关系?之前在 awk 中没见过这种情况【参考方案2】:

使用 XML 解析器处理文件。这在各方面都比使用正则表达式破解它更好。

use warnings;
use strict;

use XML::LibXML;

my $file = shift // die "Usage: $0 file\n";

my $doc = XML::LibXML->load_xml(location => $file);

my ($node) = $doc->findnodes('//value');

my $new_value = $node->to_literal =~ s/node1\-\K([0-9]+)/1+$1/er;

$node->removeChildNodes();
$node->appendText($new_value);

$doc->toFile('new_' . $file);   # or just $file to overwrite

一旦完全测试,将输出文件名更改为输入名称 ($file) 以覆盖。

像上面那样删除和添加node 是更改XML 对象的一种方法。

或者,setData 第一个孩子

$node->firstChild->setData($new_value);

其中setData 可用于textcdatacomment 类型的节点。

或者,搜索文本,然后直接使用text node

my ($tnode) = $doc->findnodes('//value/text()');

my $new_value = $tnode =~ s/node1\-\K([0-9]+)/1+$1/er;

$tnode->setData($new_value);

print $doc->toString;

还有更多。使用什么方法取决于需要做的所有事情。如果唯一的工作确实只是编辑该文本,那么最简单的方法可能是获取text 节点。

【讨论】:

感谢伙伴,非常有帮助。我以前从未使用过 XML 解析器,这比将这个文件作为纯文本读取更有意义。 @tarekeldarwiche 是的,图书馆更好。可以使用我在答案中使用的正则表达式轻松地为这个特定任务执行(Perl)单行:perl -i -pe's/node1\-\K([0-9]+)/1+$1/e' file。但这在很多方面都是自找麻烦。如果没有理由不将脚本放在磁盘上,那么使用解析器库是可行的方法。【参考方案3】:

这是一个使用 Perl 的 XML::Twig 的示例。基本上,您为节点创建一个处理程序,然后在该处理程序中执行您需要执行的任何操作。您可以查看当前文本,创建一个新字符串,并将节点文本设置为该字符串。一开始有点吓人,但是一旦你习惯了它就会非常强大。与其他 Perl XML 解析器相比,我更喜欢它,但对于非常简单的事情,它可能不是最好的工具:

#!perl
use v5.26;

use XML::Twig;

my $xml = <<~"XML";
    <attribute>
        <name>test</name>
        <type>java.lang.String</type>
        <value>node1-3</value>
    </attribute>
    XML

my $twig = XML::Twig->new(
    pretty_print  => 'indented',
    twig_handlers => 
        # the key is the name of the node you want to process
        value => sub 
            # each handler gets the twig and the current node
            my( $t, $node ) = @_;
            my $current = $node->text;
            # how you modify the text is not important. This
            # is just a Perl substitution that does not modify
            # the original but returns the new string
            my $next = $current =~ s/(\d+)\z/ $1 + 1 /re;
            $node->set_text( $next );
            
        
    );
$twig->parse( $xml );
my $updated_xml = $twig->sprint;

say $updated_xml;

XML::Twig 的一些其他内容:

我在Modify XML data with XML::Twig中给出了一个很长的例子 Perlmonks 有一个平行的例子Edit a node value in xml

【讨论】:

【参考方案4】:

只是为了好玩,我使用 Perl 的 Mojo::DOM 来使用 CSS 选择器完成相同的任务。这没有XML::Twig 强大(没有流解析!),但对于简单的事情它可以很好地工作:

#!perl
use v5.26;

use Mojo::DOM;

my $xml = <<~"XML";
    <attribute>
        <name>test</name>
        <type>java.lang.String</type>
        <value>node1-3</value>
    </attribute>
    XML

my $dom = Mojo::DOM->new( $xml );
my $node = $dom->at( 'attribute value' ); # CSS Selector

my $current = $node->text;
say "Current text is $current";

# how you change the value is up to you. This line is
# just how I did it.
my $next = $current =~ s/(\d+)\z/ $1 + 1 /re;
say "Next text is $next";

$node->content( $next );

say $dom;

它没有单行那么糟糕,但它有点冗长。 -0777 使段落模式能够在第一行读取的所有内容中啜饮(末尾有文件名命令行参数):

$ perl -MMojo::DOM -0777 -E '$d=Mojo::DOM->new(<>); $n=$d->at(q(attribute value)); $n->content($n->text =~ s/(\d+)\z/$1+1/er); say $d' text.xml
<attribute>
    <name>test</name>
    <type>java.lang.String</type>
    <value>node1-4</value>
</attribute>

Mojo 有一个 ojo 模块(因此,使用 -M,拼写为 Mojo)以牺牲声明变量为代价使这稍微简单一些。它是x()Mojo::DOM-&gt;new() 的快捷方式:

$ perl -Mojo -0777 -E 'my $d=x(<>); my $n=$d->at(q(attribute value)); $n->content($n->text =~ s/(\d+)\z/$1+1/er); say $d' text.xml
<attribute>
    <name>test</name>
    <type>java.lang.String</type>
    <value>node1-4</value>
</attribute>

【讨论】:

【参考方案5】:

我不喜欢使用面向行的文本处理来修改 XML。你失去了上下文和位置,你无法判断你是否真的在修改你认为的自己(在 cmets、CDATA 等内部)。

但是,忽略这一点,这是您可以轻松解决的单线问题。基本上,您没有正确锚定。当你想要第二组数字时,你匹配第一组数字:

$ perl -i -pe '/node1-/ && s/(\d+)(.*)/$1+1 . $2/e' filepath

相反,匹配&lt; 之前的一组数字。 (?=...) 是一个不匹配字符(只是条件)的正向前瞻,所以你不要替换那些:

$ perl -i -pe '/node1-/ && s/(\d+)(?=<)/$1+1/e' filepath

但是,我会合并第一场比赛。 \K 允许您 to ignore part of a substitution's match。你必须匹配\K之前的东西,但你不会替换那个部分:

$ perl -i -pe 's/node1-\K(\d+)/$1+1/e' filepath

同样,这些可能会奏效,但最终你(更有可能是下一个人)会被它烧死。我不知道你的情况,但正如我经常劝告人们的那样:这不是稀有,而是灾难。

【讨论】:

以上是关于在 XML 文件中查找并增加数字的主要内容,如果未能解决你的问题,请参考以下文章

在字符串中查找数字并计算,在字符串中插入(Swift)

编写linux脚本根据文档中的内容(文件名)查找文件

Python在列表或数组中查找范围之间的数字

使用 Python 在 XML 中查找和替换值

在 txt 文件中查找最小和最大数字

从包含确切数字的文件中删除一行并在第一次查找和删除时退出