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

... Server Details ...

I would like to match on the lines:


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.


You can do something like:

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

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.


Would something like this work?

sed '\|<key>servers</key>|{n
\|<dict>| r other-file.xml
}' file.xml
  • That assumes the <key>servers</key>\n<dict> doesn't follow another <key>servers</key> line. – Stéphane Chazelas Aug 8 '14 at 20:28

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


Since you added a Perl tag:

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

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.