在 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,但它增加了错误的数字,从 node1
到 node2
,而不是在破折号之后:
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)"</value>"1'
,两者都使用解析技巧来获取数字。
@tarekeldarwiche $0 !~ /node1/
位使其也打印与/node1/
不匹配的行。要将其写入文件,只需将输出通过管道传输到带有command > 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
可用于text
、cdata
或comment
类型的节点。
或者,搜索文本,然后直接使用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->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
相反,匹配<
之前的一组数字。 (?=...)
是一个不匹配字符(只是条件)的正向前瞻,所以你不要替换那些:
$ 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 文件中查找并增加数字的主要内容,如果未能解决你的问题,请参考以下文章