Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

A good mate on a forum had helped me with creating this script but everything Ive thrown at it outputs wrong and I do not know why or what is the issue. I ran all my script in ubuntu's terminal if questioned as bash test.sh.

My goal is to increment several .xml file's process></process> tags, but some files could have 1 to 100 tags.

Example:

 - jfksaJDFH
 - <process>value=""</process>
 - <process>value=""</process>
 - <process>value=""</process>
 - <process>value=""</process>
 - jdhkjasdh
 - <process>value=""</process>
 - <process>value=""</process>
 - <process>value=""</process>
 - <process>value=""</process>

After script:

 - jfksaJDFH
 - <process>value="1"</process>
 - <process>value="2"</process>
 - <process>value="3"</process>
 - <process>value="4"</process>
 - jdhkjasdh
 - <process>value="5"</process>
 - <process>value="6"</process>
 - <process>value="7"</process>
 - <process>value="8"</process>

Script:

#!/bin/bash

dir="/mnt/Desktop/test/"

while IFS= read -r -d '' file
do
    i=1
    while IFS= read -r -u 3 line
    do
        if [[ $line = '<process></process>' ]]; then
           echo "<process>value=\"$((i++))\"</process>"
        else
           echo "$line"
        fi
    done 3< "$file" > "$file.xml"
done < <(find $dir -type f -name \*.xml -print0)

when script above is ran removes last <process>value=""</process>

modify script to this:

while IFS= read -r -d '' file
do
    i=1
    while IFS= read -r -u 3 line
    do
        if [[ $line = '<process></process>' ]]; then
           echo "$line"
        else
           echo "<process>value=\"$((i++))\"</process>"
        fi
    done 3< "$file" > "$file.xml"
done < <(find $dir -type f -name \*.xml -print0)

Output of file is this:

<process>value="1"</process>
<process>value="2"</process>
<process>value="3"</process>
<process>value="4"</process>
<process>value="5"</process>
<process>value="6"</process>
<process>value="7"</process>
<process>value="8"</process>
<process>value="9"</process>
<process>value="10"</process>
<process>value="11"</process>
<process>value="12"</process>
<process>value="13"</process>
<process>value="14"</process>
<process>value="15"</process>
<process>value="16"</process>
<process>value="17"</process>
<process>value="18"</process>
<process>value="19"</process>
<process>value="20"</process>
<process>value="21"</process>
<process>value="22"</process>
<process>value="23"</process>

which in other words increments but removes all other content in the page.

share|improve this question

3 Answers 3

up vote 1 down vote accepted

put this script in a file (ex: 'increase.awk') :

BEGIN { i = 1 }
/.*<process>value=""<\/process>.*/ { split($0, a, "value=\"\"") ; print a[1] "value=\"" i++ "\"" a[2] ; next }
/.*/ { print $0 }

and then call:

gawk -f increase.awk < yourinputfile

explanation: in awk, split("string", a, "separatorstring") splits the "string" into an array called a, using "separatorstring" as separator. So a[1] contains everything until the 1st "separatorstring", then a[2] contains everything until end of line or until the next "separatorstring", etc.

share|improve this answer
    
of course you'll need to edit this if some lines of the file you're trying to edit could contain parts of the strings used to discover where to insert the counter... (ex: can your script contain commented-out lines with such strings?) –  Olivier Dulac Dec 8 '12 at 1:38
    
and your example assumed that there is only one of those on a line, but if you need to have more complicated cases, you can simply pre-treat the file to temporarily have one per lines, then put the lines back together if needed. (for this, prepend and append the <process...../process> with "begin" and "end", and put newlines before and after those, and revert once this new file has been edited by the awk script. –  Olivier Dulac Dec 8 '12 at 1:48

Have you noticed, you have reverted the if-then-else construct's logic when you rewote the script

please note the placement of lines commented with line1 and line2 below. You had them reversed in your re-written code

while IFS= read -r -d '' file
do
    i=1
    while IFS= read -r -u 3 line
    do
        if [[ $line = '' ]]; then
           echo "value=\"$((i++))\""   # line 1 ***************
        else
           echo "$line"                # line 2 ***************
        fi
    done 3 "$file.xml"
done 

hope this helps

share|improve this answer
    
Yes I am very aware of this. If you read below the sample script it says that when it is ran it deletes the last tag. –  Darth_Vader Dec 10 '12 at 17:44

Please don't do XML like this. Consider if you will - XML is a structured data type that actively ignores whitespace. Has unary tags, such as <attr name="fish" /> and other things that mean if you parse it line by line, using regular expressions one day your code will mysteriously break.

The way to do this is with an XML Parser. Which one you use is a matter of taste, but for scripting I like XML::Twig (perl module).

To solve your problem as outlined:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig;

sub increment_value {
    my ( $twig, $process ) = @_;
    my ($value) = ( $process->text =~ m/(\d+)/ );
    print "Got $value\n";
    if ( defined ( $value ) ) { 
        $process->set_text( 'value="' . ++$value . '"' );
    } 
    else {
        $process -> delete;
    }
}

my $twig = XML::Twig->new(
    'pretty_print'  => 'indented',
    'twig_handlers' => { 'process' => \&increment_value },
);
$twig->parsefile( 'your_file.xml'  );
$twig->print;    #prints to stdout.

This triggers a 'handler' for each process element, which extracts, tranforms and replaces the text.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.