如何使用 Sed 和 Awk 在 XML 中将一个 属性 替换为另一个

How to replace one property for another in XML using Sed and Awk

我有一个包含很多 XML 个节点的文件:

<output>
<file name="user.java">
</file>

<file name="random.java">
<error line="52" column="3" severity="warning" message="User is not found." source="randomSource"/>
</file>
<output/>

现在我需要用文件中的 name 属性替换错误节点中的 source 并将其打印到文件中。所以输出文件应该有 only 行错误:

<error line="52" column="3" severity="warning" message="User is not found." name="customer.java"/>

名称最好是第一个属性:

<error name="random.java" line="52" column="3" severity="warning" message="User is not found." />

所以新文件应该只包含错误节点,我只能使用默认工具,例如 sed/awk/cut/etc...

我只知道打印错误行,但不知道如何执行上述操作:

awk -vtag=file -vp=0 '{
if([=15=]~("^<"tag)){p=1;next}
if([=15=]~("^</"tag)){p=0;printf("\n");next}
if(p==1){=;printf("%s",[=15=])}
}' infile 

试试这个简单的 awk 程序:

level == 0 && [=10=] ~ "<" tag ".*>" {
    print
    level++
    # get "name" attribute
    gsub(/^.*name="/, "")
    gsub(/".*$/, "")
    name = [=10=]
    next
}
level == 1 && /<error.*>/ {
    # remove "source" attribute
    gsub(/ source="[^"]*"/, "")
    # put "name" attribute at the beginning of "error" tag
    gsub(/<error /, "<error name=\"" name "\" ")
    print
    next
}
level == 1 && [=10=] ~ "</" tag ">" {
    print
    level--
    next
}
{
    print
}

这样调用:

$ cat xmlerr.xml | awk -v tag="file" -f xmlerr.awk 
<output>
    <file name="user.java">
    </file>
    
    <file name="random.java">
    <error name="random.java" line="52" column="3" severity="warning" message="User is not found."/>
    </file>
</output>

删除不必要的 print 命令

备选方案

如果你想在打开的“file”标签中抑制“name”属性,第一个块变成:

level == 0 && [=12=] ~ "<" tag ".*>" {
    name = [=12=]
    level++
    n = gsub(/^.*name="/, "", name)
    gsub(/".*$/, "", name)
    # if substitution done, remove "name" attribute in the original line before printing
    if (n > 0) {
        gsub(/ name="[^"]*"/, "")
    }
    print
    next
}

和输出:

<output>
    <file>
    </file>
    
    <file>
    <error name="random.java" line="52" column="3" severity="warning" message="User is not found."/>
    </file>
</output>

试试这个 Perl 解决方案:

$ cat stacky.txt
<output>
<file name="user.java">
</file>

<file name="random.java">
<error line="52" column="3" severity="warning" message="User is not found." source="randomSource"/>
</file>
<output/>
   
$ perl -ne  ' /<file (name=\S+)>/ and $x=; if(/<error/) { s/(\<error)(.*)(\bsource="[^"]+")(.+)/ $x  /g  ; print }  ' stacky.txt
<error name="random.java"  line="52" column="3" severity="warning" message="User is not found."  />

假设您的输入确实如您在示例中所示那样结构化(即 <...>s 内没有换行符,每行只有一组 <...>s,并且全白 space 在每一行中都是空白字符)然后在每个 Unix 框上的任何 shell 中使用任何 awk 并使用以空白作为边界的文字字符串操作,因此即使文本中存在任何正则表达式或反向引用元字符,它也能正常工作,或者如果任何目标字符串都是其他字符串的子字符串:

$ cat tst.awk
{ tag=[=10=]; gsub(/^ *< *| .*$/,"",tag) }

(tag == "file") && match([=10=],/ name="[^"]+"/) {
    name = substr([=10=],RSTART+1,RLENGTH-1)
}

(tag == "error") && match([=10=],/ source="[^"]+"/) {
    [=10=] = substr([=10=],1,RSTART-1) substr([=10=],RSTART+RLENGTH)
    match([=10=],/ *< *[^ ]+ /)
    [=10=] = substr([=10=],1,RLENGTH) name substr([=10=],RSTART+RLENGTH-1)
}

{ print }

$ awk -f tst.awk file
<output>
<file name="user.java">
</file>

<file name="random.java">
<error name="random.java" line="52" column="3" severity="warning" message="User is not found."/>
</file>
<output/>

或者如果您更愿意将 source= 原位替换为 name=:

$ cat tst.awk
{ tag=[=12=]; gsub(/^ *< *| .*$/,"",tag) }

(tag == "file") && match([=12=],/ name="[^"]+"/) {
    name = substr([=12=],RSTART+1,RLENGTH-1)
}

(tag == "error") && match([=12=],/ source="[^"]+"/) {
    [=12=] = substr([=12=],1,RSTART) name substr([=12=],RSTART+RLENGTH)
}

{ print }

$ awk -f tst.awk file
<output>
<file name="user.java">
</file>

<file name="random.java">
<error line="52" column="3" severity="warning" message="User is not found." name="random.java"/>
</file>
<output/>

如果您只想打印“错误”行,那么在上面只需更改:

}

{ print }

至:

    print
}

所以 print 只发生在 tag == "error" 块内。