【问题标题】:Find and replace in xml file using sed使用 sed 在 xml 文件中查找和替换
【发布时间】:2010-12-20 04:21:22
【问题描述】:

我需要查找并替换特定 xml 元素的值。条件如下:

  • 元素enabled的值必须从0改为1
  • enabled 必须是 somenode 元素的子元素

我的测试 xml 如下所示:

<somenode name="node1">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</somenode>

<someothernode name="node2">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</someothernode>

<somenode name="node3">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</somenode>

我希望第一个和第三个 enabled 元素会被更改。到目前为止,我已经设法编写了这个 sed 命令:

sed -n "1h;1!H;${;g;s|\(<somenode [^>]*>\)\(.*\)\(<enabled>\s*\)0\(\s*</enabled>\)\(.*</somenode>\)|\1\2\3 1 \4\5|g;p;}" test.xml

但它只改变了最后一个,我相信这是由于贪婪匹配。 任何帮助将不胜感激。

【问题讨论】:

  • 我猜任何带有 XML 解析库的动态语言(Perl、Python、PHP、Ruby)都会比 sed 更好地完成这项任务,您使用螺丝刀吃汤有什么特别的原因吗?
  • 看看stackoverflow.com/questions/91791/…——那里提到了很多工具。

标签: xml sed replace


【解决方案1】:

尝试使用正则表达式解析 XML 通常是个坏主意。请参阅之前的讨论,例如Parsing XML with REGEX in Java。 (实际上,您的 XML 格式不正确,因为它没有一个根元素)。有许多不同的(免费)XML 引擎用于解析和操作几乎每种语言的 XML,我建议您使用其中的一种。

【讨论】:

  • Given xml 只是一个摘录,我认为不会改变重点。更一般的问题是“替换文本中给定单词的所有出现,其中该单词介于两个其他给定单词之间”。
  • 这是一个不同的问题,因为解析文本和解析 XML 并不相同。正如许多海报在我引用的页面中提到的那样,您的 XML 可能会随着时间而改变,并且 XML 的语法变体(不同的引用字符、空格、CDATA 等)可能会使问题复杂化。相同的规范 XML 有几种不同的词法形式。
【解决方案2】:

忘记sed 进行复杂的多行处理。认真的。

如果您不愿意使用适当的 XML 工具,至少使用具有适当分支语句的标准字符串处理工具 :-)

如果您可以保证您的文件按照您拥有的方式进行格式化,您可以使用以下内容:

pax> echo '<somenode name="node1">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</somenode>

<someothernode name="node2">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</someothernode>

<somenode name="node3">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</somenode>
' | awk '
    BEGIN {s = 0}
    /^<somenode / {s=1}
    /^<\/somenode>/ {s=0}
    /^    <enabled>0<\/enabled>/ {if (s==1) {$0="    <enabled>1</enabled>"}}
    {print}
'

得到:

<somenode name="node1">
    <some></some>
    <enabled>1</enabled>
    <some></some>
</somenode>

<someothernode name="node2">
    <some></some>
    <enabled>0</enabled>
    <some></some>
</someothernode>

<somenode name="node3">
    <some></some>
    <enabled>1</enabled>
    <some></some>
</somenode>

这种方法的问题在于它不能处理可能是完全有效的 XML 文件。此特定版本具有某些限制,例如:

  • somenode 开始和结束标记必须位于行首。
  • 启用的标签必须以四个空格开头。 您可以解决这些问题以使其更加灵活,但是当您编写脚本来处理 any 有效的 XML 输入时,它会变得如此可怕以至于它会更快使用 XML 转换工具。

这就是为什么最好使用专门为这项工作构建的工具。但是,如果您只是想要快速破解并且文件格式在您的控制之下,那么使用awk(或perlpython 或您选择的其他快速而肮脏的脚本工具)可能没问题。

【讨论】:

    【解决方案3】:

    你可以使用 gawk

    awk -vRS= '/somenode/{ 
        $0=gensub("(.*<enabled>)([01])(</enabled>.*)", "\\11\\3","g",$0) 
    }1'  file
    

    输出

    $ ./shell.sh
    <somenode name="node1">
        <some></some>
        <enabled>1</enabled>
        <some></some>
    </somenode>
    <someothernode name="node2">
        <some></some>
        <enabled>0</enabled>
        <some></some>
    </someothernode>
    <somenode name="node3">
        <some></some>
        <enabled>1</enabled>
        <some></some>
    </somenode>
    

    【讨论】:

      【解决方案4】:

      你似乎需要用 sed 循环一些东西

      http://www.rtfiber.com.tw/~changyj/sed/html/p.20070613a.html

      我还是想不通,仅供参考。

      【讨论】:

        【解决方案5】:

        从您的描述中可以看出,您的要求非常简单,因此如果您不想使用 XML 解析器/工具,则无需使用。您可以只使用 shell(或您可能喜欢的其他 shell 工具)

        #!/bin/bash
        while read -r line
        do 
            case "$line" in
                *"<someothernode"* ) flag=0;;
                *"<somenode"* )flag=1;;
            esac
            if [ "$flag" -eq "1" ] ;then
                case "$line" in
                    *"<enabled"* ) 
                        echo "${line/<enabled>0/<enabled>1}"
                        ;;
                    *) echo $line;
                esac
            else
                echo $line
            fi    
        done < "file"
        

        【讨论】:

          【解决方案6】:

          其他人已经解释了为什么一般是not a good idea用正则表达式处理XML。

          考虑到所有这些,下面是 sed 程序,用于在匹配 start 的行之间bar 替换匹配 foo 的文本和结束(包括):

          /start/,/end/s/foo/bar/
          

          【讨论】:

            【解决方案7】:

            尽可能使用 xmlstarlet:

            echo '
            <root>
            <somenode name="node1">
               <some></some>
               <enabled>0</enabled>
               <some></some>
            </somenode>
            
            <someothernode name="node2">
               <some></some>
               <enabled>0</enabled>
               <some></some>
            </someothernode>
            
            <somenode name="node3">
               <some></some>
               <enabled>0</enabled>
               <some></some>
            </somenode>
            </root>
            ' > testfile.xml
            
            
            xml val testfile.xml
            xml el -v testfile.xml
            
            xml ed --help
            
            # version 1
            xml ed -u "//somenode[1]/enabled" -v '1' \
                   -u "//somenode[2]/enabled" -v '1' \
                   testfile.xml  
            
            # version 2  (-L for in-place editing; xmlstarlet v1.0.2)
            xml ed -L -u "//somenode[@name='node1']/enabled" -v '1' \
                      -u "//somenode[@name='node3']/enabled" -v '1' \
                      testfile.xml  
            

            【讨论】:

              猜你喜欢
              • 2017-08-07
              • 2012-02-14
              • 1970-01-01
              • 2016-05-12
              • 2020-08-05
              • 1970-01-01
              • 2014-08-17
              • 2017-09-12
              • 1970-01-01
              相关资源
              最近更新 更多