2

I have an xml file process.xml and I want to insert the content of tmp.xml into it. But the caveat is, I need those contents to be inserted between two matching patterns. Here is a snippet of the process.xml file

$cat process.xml
...
<fork name="data">
      <path start="process_x" />
      <path start="process_y" />
      <path start="process_z" />
</fork>
...
...
<action name="process_x" />
....
....
</action>
<action name="process_z" />
....
....
</action>

And here is the content of tmp.xml file

$ cat tmp.xml
<path start="process_a" />
<path start="process_b" />

My matching pattern will be "process_z" and "</fork>" and the content should be pasted between these patterns. Here is what I tried..

string=$(tac process.xml | grep -m1 -oP '(?<=path start="process_).*(?=" />)')
search="process_$string"
sed -e "/$search/ r tmp.xml" "process.xml"

But it inserts the tmp.xml content inside the fork and action. However I need it only inside the fork like this.

...
<fork name="data">
      <path start="process_x" />
      <path start="process_y" />
      <path start="process_z" />
      <path start="process_a" />
      <path start="process_b" />
</fork>
...
...

Any help is much appreciated.

  • It looks like you want to insert the content of that file after the last occurrence of <path start="process_. Is that right? – Stéphane Chazelas Nov 15 '16 at 17:10
3

It looks like you want to insert the content of that file after the last occurrence of <path start="process_.

You could do:

awk '
  /path start="process_/ {print saved $0; saved=""; n++; next}
  n {saved = saved $0 RS; next}
  {print}
  END{system("cat tmp.xml"); printf "%s", saved}' process.xml

Though that would mean storing the part of the file from the last occurrence of path start="process_ to the end in memory.

Or you could slurp the whole files in memory with:

perl -0777 -pe 's/.*path start="process_.*?\n\K/<STDIN>/se
               ' process.xml < tmp.xml

A variant that checks for the </fork> on the next non-empty line:

perl -0777 -pe 's{.*path start="process_[^\n]*\n\K(?=\s*</fork>)}{<STDIN>}se
               ' process.xml < tmp.xml

A variant that aligns the indentation and adds an extra newline character if missing in tmp.xml:

perl -0777 -pe 's{(?s:.*)(^\h*).*path start="process_.*\n\K(?=\s*</fork>)}{
 $insert = <STDIN>;
 $indent = $1;
 $insert =~ s/^/$indent/gm;
 $insert =~ s/\n?$/\n/;
 $insert}me' process.xml < tmp.xml

With -0777 -pe 'code' file, perl runs the code, with $_ being the content of the file and prints that $_ (here modified by the code) afterwards.

In there, we have just one substitution command s{pattern}{replacement}flags.

The trick to get the last occurrence of the pattern in all those commands is the leading greedy .* (here under the s flag so it also matches newline characters). As it's greedy, it will try and match as many characters until ^ (a beginning of line with the m flag) followed with a sequence of horizontal blanks (\h*), which we capture in $1 with (\h*) followed by our pattern followed by the rest of the line (.* this time without the s flag so won't gobble newline characters) followed by a newline.

After which we add a \K to tell perl that's the start of the matched text. Then we have a look ahead operator to verify that that newline is followed by a sequence of whitespace (\s*) and </fork>.

In the replacement, we slurp the content of tmp.xml from stdin and insert the captured indentation at the start of each of its lines, add the trailing newline if missing and offer that as the replacement.


An alternative could be to process the file twice. Once to retrieve the line number of the last occurrence of the pattern, and the second to insert the file there:

sed "$(awk '/path start="process_/{n=NR};END{print n}' < process.xml  
      )r tmp.xml" process.xml

Alternatively, maybe you can just insert it before </fork>:

awk '/<\/fork>/{system("cat tmp.xml")};1' < process.xml
  • This is fine but is there a way that I can validate both match and then insert between them? What I mean is, I need to check the second pattern </fork> as well. Is that possible? – Alex Raj Kaliamoorthy Nov 16 '16 at 8:29
  • @Alex, does that </fork> have to be on the next line or anywhere further down the file, or anywhere further down the file provided there's no other closing tag in between? – Stéphane Chazelas Nov 16 '16 at 10:03
  • it is has to be on the next line. Also the inserted content of the tmp.xml file is not intended with that of process.xml and `</fork> appears in the same line of the end of the copied content. – Alex Raj Kaliamoorthy Nov 16 '16 at 10:07
  • @Alex, see edit for a perl variant that looks for </fork> on the next non-empty line. – Stéphane Chazelas Nov 16 '16 at 10:07
  • 1
    @Alex, see edit. – Stéphane Chazelas Nov 16 '16 at 11:17
0

Assuming there's only one <fork> in your file...

Add </fork> to the end of your insert string.

Replace </fork> with your modified insert string.

Update: Since the action tags also contain the same process name, expand the 'search' string so that it finds the whole tag <path start="process_$string" />. Or enough of it to eliminate the 'action' matches, like so: t="process_$string"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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