0

I try to replace string in XML file with sed, but since its regular expression doesn't support non-greedy expressions, I encounter a problem.

XML Example:

<opt>
  <Node active="yes" file="/home/user/random_filename" last_time="17/07/14-00:02:07" time_in_min="5" />
</opt>

I want to find the file attribute, which is random string, and replace it with another string.

This command replaces the string, BUT removes the trailing data.

sed 's/file=".*"/file="new_file_name"/' file.xml

Output:

<opt>
   <Node active="yes" file="new_file_name" />
</opt>

How should I handle it?

Alex
  • 33
  • 1
  • 7

3 Answers3

2

Use "[^"]*" instead of ".*":

Input:

<opt>
  <Node active="yes" file="/home/user/random_filename" last_time="17/07/14-00:02:07" time_in_min="5" />
</opt>

Command:

sed 's/file="[^"]*"/file="new_file_name"/'

Output:

<opt>
  <Node active="yes" file="new_file_name" last_time="17/07/14-00:02:07" time_in_min="5" />
</opt>
OnlineCop
  • 3,799
  • 19
  • 33
1

This doesn't use sed but when manipulating xml documents, you might want to consider xslt. It may be an over the top solution to your problem, but it's a great fit for working with xml.

filter.xsl:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" omit-xml-declaration="yes"/>

<!-- The replacement string is passed as a parameter.
     You can have as many param elements as you need. -->
<xsl:param name="NEW_NAME"/>

<!-- Copy all attributes and nodes... -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<!-- ...but when you encounter an element with a file attribute,
     replace it with the value passed in the parameter named $NEW_NAME. -->
<xsl:template match="@file">
    <xsl:attribute name="file">
        <xsl:value-of select="$NEW_NAME"/>
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

A libxslt example (add as many --stringparam name/value pairs that you need):

xsltproc --stringparam NEW_NAME new_file_name filter.xsl file.xml

Result:

<opt>
    <Node active="yes" file="new_file_name" last_time="17/07/14-00:02:07" time_in_min="5"/>
</opt>
Cole Tierney
  • 7,737
  • 1
  • 23
  • 29
  • Nice one! I will consider it. I need to change more than one argument and it looks like most convenient way. Thanks. – Alex Jul 17 '14 at 05:12
  • The match attribute xpath expression can be made more specific if needed. For example to only change file attributes in Node elements, change `match="@file"` to `match="Node/@file"`. You can also match based on the contents of the file attribute. To change only those that start with '/home/user/' use `match="Node/@file[starts-with(., '/home/user/')]"`. – Cole Tierney Jul 17 '14 at 12:35
0
$ sed 's/\(.*file="\)[^"]*/\1new_file_name/' file
<opt>
  <Node active="yes" file="new_file_name" last_time="17/07/14-00:02:07" time_in_min="5" />
</opt>
Ed Morton
  • 157,421
  • 15
  • 62
  • 152