Adding XML element in XML file using sed command in shell script
Asked Answered
L

5

10

I am using sed command to insert an xml element into the existing xml file.

I have xml file as

<Students>
    <student>
        <name>john</>
        <id>123</id>
    </student>
    <student>
        <name>mike</name>
        <id>234</id>
    </student>
</Students>

I want to add new elememt as

    <student>
        <name>NewName</name>
        <id>NewID</id>
    </student>

So my new xml file will be

<Students>
    <student>
        <name>john</>
        <id>123</id>
    </student>
    <student>
        <name>mike</name>
        <id>234</id>
    </student>
    <student>
        <name>NewName</name>
        <id>NewID</id>
    </student>
</Students>

For this I have written shell script as

#! /bin/bash

CONTENT="<student>
            <name>NewName</name>
            <id>NewID</id>
        </student>"

#sed -i.bak '/<\/Students>/ i \ "$CONTENT" /root/1.xml
sed -i.bak '/<\/Students>/ i \'$CONTENT'/' /root/1.xml

I am getting error as

sed: can't read <name>NewName</name>: No such file or directory
sed: can't read <id>NewID</id>: No such file or directory
sed: can't read </student>: No such file or directory

And in the xml file, only <student> is added. The remaining elements are not added. Does anyone know why this error?

Lacagnia answered 23/4, 2014 at 4:22 Comment(2)
Several tools exist to manipulate XML files. Use those instead of regular expressions.Estevan
@Estevan would be nice of you to list one (or two) of those toolsCavefish
D
9

change this:

CONTENT="<student>
            <name>NewName</name>
            <id>NewID</id>
        </student>"

to this:

CONTENT="<student>\n<name>NewName</name>\n<id>NewID</id>\n</student>"

and then:

C=$(echo $CONTENT | sed 's/\//\\\//g')
sed "/<\/Students>/ s/.*/${C}\n&/" file
Drove answered 23/4, 2014 at 4:54 Comment(2)
If my tag is <name>New Name</name>, then how can handle whitespace here? '\t' will add one tab and I need single whitespaceLacagnia
@USer007 You can escape the space character with backslash-spaceDoublecheck
C
6

You cannot have an unescaped newline in sed replacement text, that is $CONTENT in your example. sed uses the newline just like the shell does, to terminate a command.

If you need a newline in the replacement text, you need to precede it with a backslash.

There is another way to add text using the r option. For example:

Lets say your main file is;

$ cat file
<Students>
    <student>
        <name>john</>
        <id>123</id>
    </student>
    <student>
        <name>mike</name>
        <id>234</id>
    </student>
</Students>

You text you want to add is in another file (not variable):

$ cat add.txt
    <student>
        <name>NewName</name>
        <id>NewID</id>
    </student>

You can do (using gnu sed):

$ sed '/<\/Students>/{ 
    r add.txt
    a \</Students>
    d 
}' file
<Students>
    <student>
        <name>john</>
        <id>123</id>
    </student>
    <student>
        <name>mike</name>
        <id>234</id>
    </student>
    <student>
        <name>NewName</name>
        <id>NewID</id>
    </student>
</Students>

However, having given this option, it is still a very bad idea to parse xml with regular expression. It makes the solution very fragile and easy to break. Consider this as a learning exercise only.

Clarenceclarenceux answered 23/4, 2014 at 5:31 Comment(0)
M
2

This might work for you (GNU sed & Bash):

CONTENT='    <student>\
    <name>NewName</name>\
    <id>NewID</id>\
</student>'

sed '/<\/Students>/i\'"$CONTENT" file

Alternatively, put the new students in a file and:

sed '/<\/Students>/e cat new_student_file' file
Macegan answered 23/4, 2014 at 10:56 Comment(2)
Here if tag is like <name>New Name</name>, then how can I handle this whitespace.Lacagnia
@Optimus for the variable just remember that the last character of each line (but the last) must end in `\`. For the file solution just write the text as you see it.Macegan
D
0

Please DO NOT use sed to parse/edit XML! Use an XML-parser, like and , instead.

Xidel

With "direct element constructors" (and Xidel's own x:replace-nodes()):

$ xidel -s "input.xml" -e '
  x:replace-nodes(
    Students/student[last()],
    function($x){
      $x,
      <student><name>NewName</name><id>NewID</id></student>
    }
  )
' --output-node-format=xml --output-node-indent

With "computed constructors":

$ xidel -s "input.xml" -e '
  x:replace-nodes(
    Students/student[last()],
    function($x){
      $x,
      element student {
        element name {"NewName"},
        element id {"NewID"}
      }
    }
  )
' --output-node-format=xml --output-node-indent

XMLStarlet

$ xmlstarlet ed -O \
  -a 'Students/student[last()]' -t elem -n 'student' \
  -s 'Students/student[last()]' -t elem -n 'name' -v 'NewName' \
  -s 'Students/student[last()]' -t elem -n 'id' -v 'NewID' \
  "input.xml"

Output in all 3 cases:

<Students>
  <student>
    <name>john</name>
    <id>123</id>
  </student>
  <student>
    <name>mike</name>
    <id>234</id>
  </student>
  <student>
    <name>NewName</name>
    <id>NewID</id>
  </student>
</Students>
Distraught answered 8/9, 2024 at 23:17 Comment(0)
K
-1

My goal was to inject xml snippets from another file into pom.xml and the following code works for me. It also ensures that \t (tabs) will be taken into the new file, so it will be formated in the correct way.

function inject_plugin_into_pom {
  local plugin_file=$1
  local pom_file=$2

  # read from file into array
  declare -a xml_array
  while IFS= read -r line; do
    xml_array+=("$line")
  done < "$plugin_file"

  # inject into xml line by line
  for element in "${xml_array[@]}"; do
    # echo -e             | takes care of the \t
    # sed 's/\//\\\//g'   | replaces / with \/
    escaped=$(echo -e "$element" | sed 's/\//\\\//g')
    sed -i -e "s/<\/plugins>/\t${escaped}\n\t\t<\/plugins>/g" $pom_file
  done
}
Kaoliang answered 1/8, 2024 at 13:38 Comment(1)
Welcome to StackOverflow, and thank you for your contribution. However, while this seems superficially related to the question, it does not address the problem OP described, so it's not really an answer to this question.Leifleifer

© 2022 - 2025 — McMap. All rights reserved.