使用sed在xml文件中查找和替换

时间:2009-11-25 06:16:49

标签: xml sed replace

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

  • 元素已启用的值必须从 0 更改为 1 ;
  • 已启用必须是 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>

我希望第一个和第三个启用的元素会被更改。到目前为止,我已设法编写此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

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

7 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:4)

如果可能,请使用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  

答案 2 :(得分: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开始和结束标记必须位于该行的开头。
  • 启用的标签前面必须有四个空格。 您可以解决这些问题,使其更加灵活,但是当您编写脚本来处理任何有效的XML输入时,它会变得如此怪异,以至于它会更快使用XML转换工具。

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

答案 3 :(得分:2)

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

考虑到所有这一切,这里的sed程序替换 foo bar 匹配的文本 bar 匹配 start 结束 (包括):

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

答案 4 :(得分:0)

你可以使用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>

答案 5 :(得分:0)

您似乎需要使用sed循环播放

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

我仍然无法弄清楚,只是为了您的信息。

答案 6 :(得分:-1)

从您的描述中可以看出您的要求非常简单,因此如果您不愿意,则无​​需使用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"