Estoy tratando de buscar una cadena en un archivo XML, incrementar el número en 1 que le sigue inmediatamente y luego guardar los cambios nuevamente en ese mismo archivo. Solo hay una instancia de esta cadena.

Mi archivo se ve así:

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

Estoy tratando de cambiar 3 (después del nodo1-) e incrementarlo en 1 cada vez que ejecuto un comando. He intentado el siguiente sed, separando esa línea en 4 piezas y reemplazándola con esas 4 piezas, más un incremento. Desafortunadamente, no parece hacer nada:

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

También probé awk, lo que parece llevarme a algún lado, pero no estoy seguro de cómo volver a agregar la segunda mitad de la línea (

awk '{FS=OFS="-" }/node1/{$2+=1}1' filepath

Finalmente, probé perl, pero está incrementando el número incorrecto, de node1 a node2, en lugar de después del guión:

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

Soy nuevo en estos comandos y no soy tan sólido en mi expresión regular. Estoy tratando de hacer que este comando funcione, para poder usar esto en un script bash que estoy escribiendo. ¿Cuál es el mejor enfoque para tomar? ¿Qué comando tiene una ventaja sobre el otro? Me gustaría tener un comando de línea 1 para simplificar las cosas para más adelante.

10
tarekeldarwiche 27 abr. 2020 a las 23:13

5 respuestas

La mejor respuesta

¿Puedes simplemente codificar la parte final de la línea del nodo?

$ 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>
3
OpenSauce 27 abr. 2020 a las 20:37

Solo por diversión, utilicé Mojo :: DOM de Perl para hacer la misma tarea usando selectores CSS . Esto no es tan poderoso como XML :: Twig (¡sin análisis de flujo!), Pero para cosas simples puede funcionar bien:

#!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;

No es tan malo como una frase, pero es un poco detallado para eso. El -0777 habilita el modo de párrafo para absorber todo el contenido en la primera línea leída (hay un argumento de línea de comando de nombre de archivo al final):

$ 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 tiene un módulo ojo (por lo tanto, con -M, hechizos Mojo) que lo hace un poco más simple a expensas de declarar variables. Es x() es un atajo para 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
brian d foy 3 may. 2020 a las 07:17

Procese el archivo usando un analizador XML. Esto es mejor en todos los sentidos que piratearlo con una expresión regular.

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

Cambie el nombre del archivo de salida al nombre de entrada ($file) para sobrescribir, una vez que haya probado completamente.

Eliminar y agregar un nodo es una forma de cambiar un objeto XML. O setData en el primer hijo

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

Donde setData se puede usar en un nodo de tipo text, cdata o comment.

O busque texto y luego trabaje con un nodo de texto directamente

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;

Hay más. El método a utilizar depende de todo lo que se necesite hacer. Si el único trabajo es editar ese texto, entonces la forma más sencilla es obtener un nodo text.

8
zdim 28 abr. 2020 a las 09:21

No me gusta usar el procesamiento de texto orientado a líneas para modificar XML. Pierde contexto y posición y no puede saber si realmente está modificando lo que cree ser (comentarios internos, CDATA, etc.).

Pero, ignorando eso, aquí está su línea que tiene una solución fácil. Básicamente, no estás anclando correctamente. Coincide con el primer grupo de dígitos cuando desea el segundo:

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

En su lugar, haga coincidir un grupo de dígitos inmediatamente antes de un <. El (?=...) es una anticipación positiva que no coincide con los caracteres (solo la condición), por lo que no puede sustituirlos:

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

Sin embargo, combinaría el primer partido. El \K le permite ignorar parte del partido de una sustitución. Debe hacer coincidir las cosas antes de \K, pero no reemplazará esa parte:

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

Nuevamente, esto podría funcionar, pero eventualmente usted (probablemente el próximo tipo) se quemará. No sé tu situación, pero como a menudo aconsejo a la gente: no es la rareza, es la calamidad.

4
brian d foy 30 abr. 2020 a las 23:30

Aquí hay un ejemplo usando XML :: Twig de Perl. Básicamente, creas un controlador para un nodo, luego haces lo que necesites hacer en ese controlador. Puede ver el texto actual, crear una nueva cadena y establecer el texto del nodo en esa cadena. Al principio es un poco intimidante, pero es muy poderoso una vez que te acostumbras. Prefiero esto a otros analizadores XML Perl, pero para cosas muy simples, podría no ser la mejor herramienta:

#!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;

Algunas otras cosas para leer para XML :: Twig:

6
brian d foy 27 abr. 2020 a las 21:38