5

I've got an XML file I need to append to after a specific set of tags. Basically the file structure looks like this:

<key>servers</key>
<dict>
... Server Details ...
</dict>

I would like to match on the lines:

<key>servers</key>
<dict>

and append a block of XML specifying a server connection immediately after <dict>. I've got the new XML block in its own text file as it's quite long.

I've looked at sed and awk to do this but I've come up against a wall. I've also seen examples of using perl to accomplish this but I'm not so familiar with Perl. From what I understand though, sed and awk aren't great at multiline matching.

The reason I need to do multiline matching is because the <dict> tag is used frequently within the XML file and I need to append a block into the <key>servers</key> section, as opposed to replacing its entire contents.

5 Answers 5

3

You can do something like:

awk '{print}
     $0 == "<dict>" && previous == "<key>servers</key>" {
       system("cat other-file.xml")
     }
     {previous = $0}'
3
sed '/keys_line_1/,/keys_line_last/{/keys_line_last/{
h;s/unique_split_point.*//;r /path/to/insert/file
x;s/.*unique_split_point//;G
}}'

sed is not exactly forgiving when it comes to requiring adjustments to an hypothesis. Everything sed does is a direct result of the thing it has just done, and so a very minor error in detail can drastically alter results.

In this way, without a little bit of crazy and a whole lot of patience, sed script debugging can be a frustrating affair, but, given those qualities, the rewards can be significant. I highly recommend using the look command during the process as much as you might if you make the attempt.

sed ...;l;...complicated_script...;l;...

Actually, looking a second time at your question, and I think it is easier than I at first assumed. All you really need (I think) is to expand pattern space by one line in a servers match case. By default sed buffers only a line per cycle, but the commands N, P, and D provide direct control over this behavior.

In fact, I believe I misunderstood the question originally - I though you wanted to insert some text into a line, not into a block of lines.

So you just maybe need:

sed '\|<key>servers</key>|,/<dict>/N;P
/\n<dict>/!D;s/.*\n//;r /path/to/file'

That way all the lines but those in which you are interested - the range from your server through your dict match are elided from the edit buffer as soon as might be, and you only r out your target file following the first dict match which follows any sequence of your *server, and the only point at which file is ever appended to the out buffer directly follows your dict match if it is preceded by a \newline.

1

Would something like this work?

sed '\|<key>servers</key>|{n
\|<dict>| r other-file.xml
}' file.xml
1
  • That assumes the <key>servers</key>\n<dict> doesn't follow another <key>servers</key> line. Commented Aug 8, 2014 at 20:28
0

Could you be a little more specific on the result you expect ? I'd use python3 and a

PATH = '/My/Path/'

FILE = 'MyFile.xml'

for i, line in enumerate(open(PATH+FILE, 'r')):

... # 

since it's easy to catch a \n, and a line ends with one, so finding the lines you're looking for is easy

but I need a better understanding of the result to continue

0

Since you added a Perl tag:

perl -pE 'BEGIN{
              $/ = "<key>servers</key>\n<dict>\n"; 
              $content = `cat file.xml`
          }
          $_.=$content' your_input_file

You must log in to answer this question.

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