bash脚本:如何将这些匹配模式的行替换为另一行?

时间:2015-10-20 12:55:26

标签: xml bash replace sed

我需要使用sedbash脚本中的类似实用程序处理当前目录中的一组XML文件。

在每个具有以下任一行的文件中(文件中可能有0或1个)

    <MetaDatum key="Pr" value="VALUE (foobar)" />
    <MetaDatum key="Pr" value="VALUE (xyz12345678)" />

我需要用

替换整行
    <MetaDatum key="Pr" value="VALUE" />

所以我需要做的就是将VALUE (foobar)VALUE (xyz12345678)映射到VALUE

那么我应该在这个循环中使用什么操作:

for f in `grep -l "MetaDatum key=\"Pr\" value=\"VALUE" *.xml`
do
     # replace one entire line in $f with '<MetaDatum key="Pr" value="VALUE" />'
done

4 个答案:

答案 0 :(得分:2)

使用正确解析XML的工具,而不是sed。例如,在xsh中,您可以使用

for $file in { glob '*.xml' } {
    open $file ;
    for //MetaDatum/@value
        set . xsh:subst(., 'VALUE \(.*', 'VALUE') ;
    save :b ;
}

答案 1 :(得分:1)

假设grep命令中的模式标识了需要修改的所有行而没有其他行,您可以编写一个匹配相同行的sed命令,并替换该值。它上面的value属性:

sed '/MetaDatum key="Pr" value="VALUE/ s/value="[^"]*"/value="VALUE"/' $f

但请注意,该方法(grepsed)对XML的确切细节非常敏感。它会落在不同数量的空白上 - 特别是嵌入的换行符 - 额外的属性,不同的引号选择等等。

其中一些可以通过更智能的模式来解决,但其他模式则无法解决。要正确处理XML,您需要真正的XML工具。在这种情况下,适当的工具将是XSLT转换。这是一个可以完成工作的转换(前提是源文件没有覆盖默认的XML命名空间 - 谢谢CharlesDuffy):

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- identity transform: anything not otherwise matched is copied verbatim -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!--
    -- Transform the 'value' attribute of MetaDatum elements where the
    -- element has a 'key' attribute with value 'Pr', and the 'value'
    -- attribute's own value starts with 'VALUE'.
    -->
  <xsl:template match="MetaDatum[@key = 'Pr']/@value[substring(., 1, 5) = 'VALUE']">
    <xsl:attribute name="value">VALUE</xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

您可以通过任何XSLT处理器应用它,但其中一个更常见的是xsltproc,它与GNOME的libxslt一起提供。如果转换存储在文件meta.xsl中,则通过$f将转换后的输出替换为文件xsltproc的命令可能是:

temp=`mktemp` && xsltproc meta.xsl "$f" > "$temp" && mv "$temp" "$f"

正如@CharlesDuffy在评论中观察到的那样,这可能导致$f命名的文件具有与以前不同的所有权和/或更多限制权限。如何解决该问题取决于可用的工具。例如,虽然标准chownchmod没有它,但GNU版本具有设置文件的所有权和权限以匹配不同文件的机制。此外,当$f命名符号链接(替换链接或修改它指向的文件)时,您将需要考虑所需行为。由于这些是依赖于环境和前馈的问题,如果上面提到的命令没有按照您的意愿处理它们,那么您将需要决定如何修改方法。

如果您需要处理重写的默认XML命名空间,那么模板将需要更复杂一些。您需要为MetaDatum元素的名称空间及其属性声明名称空间前缀,并在您引用这些名称的任何位置使用它。

答案 2 :(得分:1)

您无法可靠地使用sed来完成这项工作:XML可以用太多不同的方式编写。 (例如,您的文档可以在与它们应用的值不同的行上具有键和值属性,或者可以在&#34;键&#34;之前放置&#34;值&#34;或者可以开始使用命名的命名空间并因此在事物上添加foo:前缀)。我们无法保证您的输入文件的未来版本将使用完全相同的格式生成,特别是生成它的代码会发生更改。

相反,请使用支持XML的工具,例如XMLStarlet

xmlstarlet ed \
  -u '//MetaDatum[@key="Pr"]/@value' \
  -v "VALUE" \
  <in.xml >out.xml

请注意,如果文件中的封闭范围内有xmlns="..."声明,则会将表达式更改为略高一点。 (这也意味着您的文件格式使用名称空间,因此特别有可能以sed无法处理的方式进行更改!)

例如,如果您的文件顶部以<root xmlns="http://example.com/foo">开头,那么您需要执行以下操作:

xmlstarlet ed \
  -N "foo=http://example.com/foo"
  -u '//foo:MetaDatum[@key="Pr"]/@value' \
  -v "VALUE" \
  <in.xml >out.xml

顺便说一下 - 如果您更喜欢就地执行修改,xmlstarlet ed有一个-i选项可以进行内联更改;因此:xmlstarlet ed -i [...] changeme.xml会写出changeme.xml的修改版本,允许利用其他一些答案显示的find个单行。

答案 3 :(得分:0)

使用此sed one-liner命令修改所有xml文件(在当前目录中):

sed -i 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' *.xml

您还可以制作先前版本的备份副本,在-i切换后添加内容,例如.bak后缀(或~ if:

sed -i.bak 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' *.xml

将命令与find工具结合使用,将sed应用于扩展名为.xml的文件(不区分大小写),可以在目标目录或其子目录中找到:

find ${targetDir} -type f -iname "*.xml" -exec sed -i 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' {} \;