4

I have an xml file (client_23.xml) in which I need to alter one line and remove one whole tag from it so I came up with perl script:

In my xml file, I have a block like this. There will be only one instance of <hello>collect_model = 1</hello> in my xml file:

<world>
    <hello>collect_model = 1</hello>
    <hello>enable_data = 0</hello>
    <hello>session_ms = 2*60*1000</hello>
    <hello>max_collect = string_integer($extract("max_collect"))</hello>
    <hello>max_collect = parenting(max_collect, max_collect, 1.0e99)</hello>
    <hello>output('{')</hello>
</world>

I need to change that line to like this: <hello>collect_model = 0</hello> so my whole block should be like this after the change:

<world>
    <hello>collect_model = 0</hello>
    <hello>enable_data = 0</hello>
    <hello>session_ms = 2*60*1000</hello>
    <hello>max_collect = string_integer($extract("max_collect"))</hello>
    <hello>max_collect = parenting(max_collect, max_collect, 1.0e99)</hello>
    <hello>output('{')</hello>
</world>

Second thing I need to remove this whole tag from the same xml file:

<derta-config>
    <data-users>2000</data-users>
    <test-users>2000</test-users>
    <attributes>hello world</attributes>
    <client-types>Client1</model-types>
    <target>price.world</target>
</derta-config>

So I have below shell script in which I am using perl which tries to do above two things along with replacing some content in a file (which I am doing for some other purpose) but the portion which I added specifically for above two doesn't work and it starts printing bunch of errors:

perl -0pe "s#<eval>collect_model = 0</eval>#<eval>collect_model = 1</eval> s#<derta-config>.* </derta-config>##sm;   s#<function>\s*<name>DUMMY_FUNCTION.+?</function>#$file#sm" client_"$client_id".xml > "$word"_new_file.xml

So I am thinking, can we do this in shell script instead, meaning remvoe the the above two things using shell script and then the output of that, we can pass to my perl script which is working on third step. So we can pass the output of shell script which will remove the above two things for me to this below perl script? Is this possible to do?

perl -0pe "s#<function>\s*<name>DUMMY_FUNCTION.+?</function>#$file#sm" client_"$client_id".xml > "$word"_dyn_model.xml

Here $client_id is 23 and $word is abc.

I am just trying to make this work and whatever is the easiest way will do for me. I will only have one instance of all the above two things I mentioned.

2 Answers 2

3

Please - don't use regular expression for parsing XML. It's a bad idea. The major reason it's a bad idea is because there's a lot of variety in XML - and some semantically identical XML can have some significantly different pattern matching.

Consider linefeeds, whitespace, unary tags etc.

<element />
<element></element>

Are both the same - and then you can indent, linefeed, split tags etc.:

<element 
    att1="fish"
    att2="carrot">

Is also valid.

So I would strongly suggest 'use a parser'. Perl has several options - I like XML::Twig:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig->new( 'pretty_print' => 'indented_a' )->parse( \*DATA );

foreach my $hello ( $twig->findnodes('//hello') ) {
    if ( $hello->trimmed_text =~ m/collect_model/ ) {
        $hello->set_text('collect_model = 0');
    }
}

$_->delete for $twig->findnodes('//derta-config');

$twig->print;

__DATA__
<root>
<world>
    <hello>collect_model = 1</hello>
    <hello>enable_data = 0</hello>
    <hello>session_ms = 2*60*1000</hello>
    <hello>max_collect = string_integer($extract("max_collect"))</hello>
    <hello>max_collect = parenting(max_collect, max_collect, 1.0e99)</hello>
    <hello>output('{')</hello>
</world>
<derta-config>
    <data-users>2000</data-users>
    <test-users>2000</test-users>
    <attributes>hello world</attributes>
    <client-types>Client1</client-types>
    <target>price.world</target>
</derta-config>
</root>

Because you seem to like one-linering in perl:

perl -MXML::Twig -0777 -e 'my $twig = XML::Twig->parse (<>); $_->set_text("collect_model = 0") for grep { $_->text =~ m/collect_model/ } $twig->findnodes("//hello"); $_->delete for $twig->findnodes("//derta-config"); $twig -> print;'
2

With this as the sample input file:

$ cat client_23.xml 
<world>
    <hello>collect_model = 1</hello>
    <hello>enable_data = 0</hello>
    <hello>session_ms = 2*60*1000</hello>
    <hello>max_collect = string_integer($extract("max_collect"))</hello>
    <hello>max_collect = parenting(max_collect, max_collect, 1.0e99)</hello>
    <hello>output('{')</hello>
</world>
<derta-config>
    <data-users>2000</data-users>
    <test-users>2000</test-users>
    <attributes>hello world</attributes>
    <client-types>Client1</model-types>
    <target>price.world</target>
</derta-config>

We can make both changes using:

$ sed 's|<hello>collect_model = 1</hello>|<hello>collect_model = 0</hello>|; \|<derta-config>|,\|</derta-config>|d' client_23.xml 
<world>
    <hello>collect_model = 0</hello>
    <hello>enable_data = 0</hello>
    <hello>session_ms = 2*60*1000</hello>
    <hello>max_collect = string_integer($extract("max_collect"))</hello>
    <hello>max_collect = parenting(max_collect, max_collect, 1.0e99)</hello>
    <hello>output('{')</hello>
</world>

How it works

We have two sed commands. The first is a substitute, the second is a delete:

  • s|<hello>collect_model = 1</hello>|<hello>collect_model = 0</hello>|

    Substitute commands have the form s|old|new|. So, here old is the original <hello>collect_model = 1</hello> and new is the replacement <hello>collect_model = 0</hello>.

  • \|<derta-config>|,\|</derta-config>|d

    This defines a range of lines. The starting line contains derta-config> and the ending line contains </derta-config>. All lines within this range are deleted by the delete command d.

6
  • Lol, looks sed is the solution for everything. Can you help me on integrating this with my perl script? I mean output of this will become the input of perl script for me? Sep 17, 2015 at 6:30
  • It sounds like you want a pipeline: sed commands filename | perl ...
    – John1024
    Sep 17, 2015 at 6:32
  • hmmm but if you see my perl command, it is already working on client_23.xml file so that means I need to remove that? Sep 17, 2015 at 6:36
  • It does work with the way you suggested. But I am thinking may be we can get rid of perl script altogether if we can do that in shell script using sed or something else. If you are around, I can explain what is that perl command doing. Sep 17, 2015 at 6:55
  • @user1950349 Yes, remove the file name from the perl command. That way perl will get its input from the pipeline.
    – John1024
    Sep 17, 2015 at 6:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .