【问题标题】:How to replace one property for another in XML using Sed and Awk如何使用 Sed 和 Awk 在 XML 中将一个属性替换为另一个属性
【发布时间】:2021-08-23 07:24:28
【问题描述】:

我有一个包含大量 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/>

现在我需要将错误节点中的source 替换为文件中的name 属性并将其打印到文件中。所以输出文件应该有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($0~("^<"tag)){p=1;next}
if($0~("^</"tag)){p=0;printf("\n");next}
if(p==1){$1=$1;printf("%s",$0)}
}' infile 

【问题讨论】:

  • Don't Parse XML/HTML With Regex. 我建议使用 XML/HTML 解析器(xmlstarlet、xmllint ...)。外科医生也不使用电锯进行手术。
  • 获得许可,然后。
  • edit您的问题显示您发布的输入的预期输出,因为不同的人回答正在猜测您可能想要的不同输出。

标签: awk sed


【解决方案1】:

试试这个简单的awk 程序:

level == 0 && $0 ~ "<" tag ".*>" {
    print
    level++
    # get "name" attribute
    gsub(/^.*name="/, "")
    gsub(/".*$/, "")
    name = $0
    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 && $0 ~ "</" 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 && $0 ~ "<" tag ".*>" {
    name = $0
    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>

【讨论】:

  • 非常感谢您的回复。它几乎就在那里。是否可以更新代码以仅打印错误节点。目前它打印文件节点,其中有 2 个错误节点。旧的一个加上被改造的那个。
  • 鉴于name 的各种值,这将失败,例如name="=my-name="name="black&amp;white" 或者如果传入的 tag 是某个其他字段的子字符串,例如filefiletype 都存在,或者是否存在其他以 error 开头的标签,例如errorHandling,或者如果 groupname= 之类的属性存在于 file 标记中。在大多数情况下你真的想要sub()时,你也在到处使用gsub()
  • 对于这样的任务,您必须确保您在匹配的字符串周围有某种边界,并使用字符串而不是正则表达式运算符与任何输入字符串,其中,事情,意味着您不能从输入中读取字符串,然后在任何正则表达式或正则表达式替换上下文中使用它,例如作为*sub() 的第一个或第二个参数,除非您首先清理所有元字符(例如,参见stackoverflow.com/q/29613304/1745001)。
  • 你好 Stacky,你到底想要什么?抑制节点“&lt;file...&gt;”+“&lt;/file&gt;”还是只抑制“file”标签中的属性“name”?在第一种情况下,删除“打印”命令。在第二种情况下,请查看我的回复中的新 ALTERNATIVE
  • 感谢您对此的解释。我将最后一个标记为解决方案,因为它按预期工作,但支持您所有的 cmets 和答案。
【解决方案2】:

试试这个 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=$1; if(/<error/) { s/(\<error)(.*)(\bsource="[^"]+")(.+)/$1 $x $2 $4/g  ; print }  ' stacky.txt
<error name="random.java"  line="52" column="3" severity="warning" message="User is not found."  />

【讨论】:

  • 感谢您的回复。但是,当我运行第二个命令时,名称的值仍然是 randomSource 而不是 random.java。
  • 再次感谢您的回复。我试过了还是有问题。也更喜欢使用 sed/awk。
  • perl 版本是什么
  • 是perl 5版本30。
【解决方案3】:

假设您的输入确实是您在示例中显示的结构(即&lt;...&gt;s 中没有换行符,每行只有一组&lt;...&gt;s,每行中的所有空格都是空白字符)然后使用每个 Unix 机器上的任何 shell 中的任何 awk 并使用以空格作为边界的文字字符串操作,因此即使文本中存在任何正则表达式或反向引用元字符,或者任何目标字符串是其他字符串的子字符串,它也可以工作:

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

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

(tag == "error") && match($0,/ source="[^"]+"/) {
    $0 = substr($0,1,RSTART-1) substr($0,RSTART+RLENGTH)
    match($0,/ *< *[^ ]+ /)
    $0 = substr($0,1,RLENGTH) name substr($0,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= in-situ:

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

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

(tag == "error") && match($0,/ source="[^"]+"/) {
    $0 = substr($0,1,RSTART) name substr($0,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" 块内。

【讨论】:

    猜你喜欢
    • 2015-06-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-05
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 2012-03-15
    相关资源
    最近更新 更多