【问题标题】:bash scripting: how do I replace these lines matching a pattern with another line?bash 脚本:如何将这些与模式匹配的行替换为另一行?
【发布时间】:2016-01-19 03:33:33
【问题描述】:

我需要使用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

【问题讨论】:

  • 该行的其余部分有多少是您要编辑的行所独有的? &lt;MetaDatum 够吗?你需要&lt;MetaDatum key="Pr"吗?更多的?这些引号总是双引号吗?他们总是自己在线吗?如果您对这些问题没有可靠的答案,那么您可能应该考虑使用旨在操纵 XML 的东西。像 XSLT 或 xmlstarlet。
  • @EtanReisner: grep -l 返回文件。
  • 是的,谢谢,忘记了-l。你的 q 的其余部分:&lt;MetaDatum key="Pr" 就足够了。我需要修改的行总是自己的。是的,总是"
  • 顺便说一句,对于一般情况(不是 XML),这是一个常见问题解答:mywiki.wooledge.org/BashFAQ/021
  • 除了我们已经提到的情况之外,sed 会做错事,顺便说一句。它对 XML cmets 一无所知;它对 CDATA 部分一无所知;它不知道如何转义值以使其在 XML 中有效(考虑您的值是否包含 &amp;amp; 并且需要替换为 &amp;amp; 以生成有效文件)。使用 XML 感知工具的部分优势在于,它们会以错误退出而不是生成无效的输出文件,但 sed 会整天生成无效的 XML 文件。

标签: xml bash replace sed


【解决方案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 在 cmets 中观察到的那样,这可能会导致由 $f 命名的文件拥有不同的所有权和/或更严格的权限。您如何解决该问题取决于可用的工具。例如,虽然标准chownchmod 没有它,但GNU 版本具有设置文件所有权和权限以匹配不同文件的机制。此外,您需要考虑$f 命名符号链接(替换链接,或修改它指向的文件)时所需的行为。由于这些是依赖于环境和偏好的问题,如果上面提供的命令不能按照您的喜好处理它们,那么您需要决定如何修改该方法。

如果您需要处理被覆盖的默认 XML 命名空间,那么模板将需要稍微复杂一些。您需要为 MetaDatum 元素及其属性的命名空间声明一个命名空间前缀,并在您引用这些名称的任何地方使用它。

【讨论】:

  • 优秀。此处未涵盖的唯一情况是 OP 的实际文件是否覆盖了其默认命名空间。
  • 顺便说一句,单线的一个警告 - 它可能会使权限更加严格(因为mktemp 使用保守的umask)。如果这是一个 GNU 系统,您可以使用 chown --reference="$f"chmod --reference="$f" 等来复制所有权和权限。
  • @CharlesDuffy,所有优点。我已经修改了我的答案来讨论它们,尽管我没有修改样式表或单行。我认为让 OP 自己根据他的具体需求调整答案是合理的,无论需要什么程度。
【解决方案2】:

你不能可靠地使用sed 来完成这项工作:XML 可以用太多不同的方式写出。 (例如,您的文档可以将键和值属性与它们应用的值放在不同的行上,或者可以将“值”放在“键”之前,或者可以开始使用命名空间,从而在事物上添加 foo: 前缀) .无法保证您的输入文件的未来版本将以完全相同的格式生成,尤其是在生成它的代码发生更改时。

改为使用可识别 XML 的工具,例如 XMLStarlet

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

请注意,如果在文件的封闭范围内有 xmlns="..." 声明,这将稍微改变上面的表达式。 (这也意味着您的文件格式正在使用命名空间,因此特别有可能以 sed 无法处理的方式发生变化!)

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

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】:

    使用这个 sed 单行命令修改所有 xml 文件(在当前目录内):

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

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

    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" />,' {} \;
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-21
        • 1970-01-01
        • 2018-08-12
        • 1970-01-01
        • 2021-03-15
        • 2021-04-02
        • 2017-11-25
        • 2016-12-17
        相关资源
        最近更新 更多