0

I'm trying to update a line in an xml file using a custom shell function and sed

In cmd line, I run it as follow: updateloc db_name

However it does not update anything. Below sample of the code

updateloc(){
 db_name=$1
 file="file.xml"
 olddb="<dbname><![CDATA[olddb]]></dbname>"
 newddb="<dbname><![CDATA[$db_name]]></dbname>"
 sed -i '' 's/$olddb/$newdb/g' $file

}

1
  • BTW, if you don't use local explicitly, then all your shell variables you create in functions are global. Commented Dec 15, 2015 at 19:34

2 Answers 2

1

The right tool for this job is XMLStarlet. To modify any element named dbname with the new value:

updateloc() {
  local db_name=$1 file=file.xml
  xmlstarlet ed --inplace -u '//dbname' -v "$db_name" "$file"
}

To replace only elements with the old value olddb:

updateloc() {
  local db_name=$1 file=file.xml
  xmlstarlet ed --inplace \
    -u "//dbname[. = 'olddb']" -v "$db_name" "$file"
}

Note that while the serialization generated by XMLStarlet won't necessarily use CDATA, it is guaranteed to be semantically equivalent, and to behave the precise same way in any XML-compliant parser.

Sign up to request clarification or add additional context in comments.

5 Comments

indeed it works as well as solution proposed below. But one more thing is that the "olddb" is a dynamic value which can be any string (only letters)...how would i introduce regex in this syntax ?
To be clear: You have multiple database definitions in the same config file, and you need to change only a specific one -- and all of them besides that one have names that don't match your regex? (If you only have one database definition, why can't you use the first form?)
If it's a matter of the dbname element name being used for different things in different contexts, the best-practice way to filter is not on content but on context -- on where in the config file the one you want to change is, ie. what its parent and grandparent elements are.
(XQuery 2.0 and 3.0 have proper regex support, but 1.0 [provided by libxml, the library used by XMLStarlet] does not. There are some extensions available, but I'd need to do some research to see if they're actually available/enabled in this context).
...the other best-practice approach being having a static id attribute on any element you expect to need to programatically address, at which point you could refer to "//dbname[@id='whichever']" without worrying about what its content is at the moment. You'll see folks doing web work do the same thing, adding unique IDs to elements they want to access from their javascript.
0

1) $olddb and $newdb are not getting expanded because they're in single quotes

2) Your sed command is getting tripped up by all those xml characters - '[' and '/' are both meaningful to sed. You'd have to escape all those, and perhaps use a different regex delimiter (e.g. 's#$olddb#$newdb#g'). It's probably a bad idea to use sed for this, unless the format of file.xml is very consistent (the closing tag could be on a separate line for example).

That said, this would work for your example:

olddb='<dbname><!\[CDATA\[olddb\]\]></dbname>'
newdb='<dbname><!\[CDATA\[newdb\]\]></dbname>'
sed -i '' "s#$olddb#$newdb#g" $file

Grep and Sed Equivalent for XML Command Line Processing has some good approaches for better ways to mangle xml from the command line.

1 Comment

indeed it works. one more thing is that the "olddb" in $olddb is a dynamic value which can be any string (only letters)...how would i introduce regex in this syntax ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.