Найти и увеличить число в файле XML - PullRequest
7 голосов
/ 27 апреля 2020

Я пытаюсь найти строку в файле 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+=1}1' filepath

Наконец, я попытался perl, но он увеличил неправильное число, с node1 до node2, а не после da sh:

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

Я новичок в этих командах и не так solid в своем регулярном выражении. Я пытаюсь заставить эту команду работать, чтобы я мог использовать ее в bash сценарии, который я пишу. лучший подход? Какая команда имеет преимущество перед другой? Мне бы хотелось иметь строковую команду 1, чтобы упростить ситуацию на потом.

Ответы [ 5 ]

9 голосов
/ 28 апреля 2020

Обработка файла с использованием синтаксического анализатора 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), чтобы перезаписать его после полной проверки.

Удаление и добавление узла - это один из способов изменить объект XML. Или setData для первого дочернего элемента

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

, где setData может использоваться на узле типа text, cdata или comment.

Или найдите текст, а затем работайте с текстовым узлом напрямую

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 узел.

6 голосов
/ 28 апреля 2020

Вот пример использования Perl s 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 :

5 голосов
/ 28 апреля 2020

Ради интереса я использовал 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>
4 голосов
/ 28 апреля 2020

Мне не нравится использовать линейно-ориентированную обработку текста для изменения XML. Вы теряете контекст и позицию, и вы не можете сказать, действительно ли вы изменяете то, что вы думаете (внутри комментариев, CDATA и т. Д. c).

Но, игнорируя это, вот ваша однострочная легко исправить. По сути, вы не привязываетесь правильно. Вы сопоставляете первую группу цифр, когда хотите вторую:

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

Вместо этого сопоставляйте группу цифр непосредственно перед <. (?=...) - это позитивный прогноз, который не соответствует символам (только условие), поэтому вы не заменяете их:

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

Однако я бы объединил первое совпадение. \K позволяет вам игнорировать часть совпадения замены . Вы должны сопоставить материал до \K, но вы не замените эту часть:

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

Опять же, это может сработать, но в итоге вы (скорее всего, следующий парень) сгорят от этого , Я не знаю вашей ситуации, но, как я часто советую людям: это не редкость, а беда.

4 голосов
/ 27 апреля 2020

Вы можете просто жестко закодировать последнюю часть строки узла?

$ 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...