如何使用Bash脚本编辑XML?

18
<root>
<tag>1</tag>
<tag1>2</tag1>
</root>

需要从bash中更改值1和2


改成什么?如果这是您的输入,那么您想要的输出是什么? - Anders Lindahl
从全局变量中更改值 - Roman
使用sed的解决方案:sed 's#<tag>([^<][^<]*)</tag>#<tag>SOMETHING</tag>#'test.xml -i - Roman
@StefanoBorini,这和什么有关系?在bash中有很多非正则表达式的XML操作工具。 - Charles Duffy
@Roman,如果你不在乎正确性的话,那只是一个“解决方案”。例如,在CDATA部分中的<tag>根本不是标签,而是文本;在<!---->之间的<tag>是注释。在具有xmlns=http://example.com/foo子树下的<tag>应该是{http://example.com/foo}tag,而不是tag。没有任何sed表达式能够了解XML语法的复杂性。 - Charles Duffy
4个回答

27

使用 XMLStarlet,将 tag 的值更改为 2,将 tag1 的值更改为 3

xmlstarlet ed \
  -u '/root/tag' -v 2 \
  -u '/root/tag1' -v 3 \
  <old.xml >new.xml

使用您的示例输入:


xmlstarlet ed \
  -u '/root/tag' -v 2 \
  -u '/root/tag1' -v 3 \
  <<<'<root><tag>1</tag><tag1>2</tag1></root>'

...作为输出发出:

<?xml version="1.0"?>
<root>
  <tag>2</tag>
  <tag1>3</tag1>
</root>

我刚刚检查过,这个工具可以通过在Debian(甚至是Debian Jessie)和Ubuntu上使用“apt-get install xmlstarlet”轻松安装。 - FibreFoX

12

你可以使用xsltproc命令(在基于Debian的发行版上的包xsltproc中)及以下XSLT表格:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:param name="tagReplacement"/>
  <xsl:param name="tag1Replacement"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>

  </xsl:template>
  <xsl:template match="tag">
    <xsl:copy>
      <xsl:value-of select="$tagReplacement"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="tag1">
    <xsl:copy>
      <xsl:value-of select="$tag1Replacement"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

然后使用以下命令:

xsltproc --stringparam tagReplacement polop \
         --stringparam tag1Replacement palap \
         transform.xsl input.xml

或者你也可以使用正则表达式,但是通过正则表达式修改XML是纯粹的邪恶 :)


10

因为Python安装在你登录的所有服务器上,所以我在Python中提供我的意见。

import sys, xml.etree.ElementTree as ET

data = ""
for line in sys.stdin:
    data += line

tree = ET.fromstring(data)

nodeA = tree.find('.//tag')
nodeB = tree.find('.//tag1')

tmp = nodeA.text
nodeA.text = nodeB.text
nodeB.text = tmp 

print ET.tostring(tree)

这个从标准输入读取,所以你可以像这样使用它:

$ echo '<node><tag1>hi!</tag1><tag>this</tag></node>' | python xml_process.py 
<node><tag1>this</tag1><tag>hi!</tag></node>

编辑 - 接受挑战

这是一个可用的xmllib实现(应该可以在Python 1.6及以上版本中使用)。我认为用叉子戳自己的眼睛会更有趣。关于这个实现,我只能说它在给定的用例中运行良好。

import sys, xmllib

class Bag:
    pass

class NodeSwapper(xmllib.XMLParser):
    def __init__(self):
    print 'making a NodeSwapper'
    xmllib.XMLParser.__init__(self)
    self.result = ''
    self.data_tags = {}
    self.current_tag = ''
    self.finished = False

    def handle_data(self, data):
    print 'data: ' + data

    self.data_tags[self.current_tag] = data
    if self.finished:
       return

    if 'tag1' in self.data_tags.keys() and 'tag' in self.data_tags.keys():
        b = Bag()
        b.tag1 = self.data_tags['tag1']
        b.tag = self.data_tags['tag']
        b.t1_start_idx = self.rawdata.find(b.tag1)
        b.t1_end_idx = len(b.tag1) + b.t1_start_idx
        b.t_start_idx = self.rawdata.find(b.tag)
        b.t_end_idx = len(b.tag) +  b.t_start_idx 
        # swap
        if b.t1_start_idx < b.t_start_idx:
           self.result = self.rawdata[:b.t_start_idx] + b.tag + self.rawdata[b.t_end_idx:]
           self.result = self.result[:b.t1_start_idx] + b.tag1 + self.result[b.t1_end_idx:]
        else:
           self.result = self.rawdata[:b.t1_start_idx] + b.tag1 + self.rawdata[t1_end_idx:]
           self.result = self.result[:b.t_start_idx] + b.tag + self.rresult[t_end_idx:]
        self.finished = True

    def unknown_starttag(self, tag, attrs):
    print 'starttag is: ' + tag
    self.current_tag = tag

data = ""
for line in sys.stdin:
    data += line

print 'data is: ' + data

parser = NodeSwapper()
parser.feed(data)
print parser.result
parser.close()

Python无处不在,没错。Python新版本中标准库里有ElementTree...这个有点儿玄乎。 - Charles Duffy
太对了。但我宁愿用叉子刺自己的眼睛,也不想尝试使用xmllib来可靠地完成这个任务。实际上,这是使用Ruby的一个很好的理由(除非你恰好使用Solaris或HP-UX机器,在这种情况下我们最终会使用Perl)。 - stringy05
2
好的。在付出了那么多的努力之后,我不能不点个赞。 :) - Charles Duffy

7

既然您在评论中提供了sed示例,我想您希望得到一个纯bash解决方案?

while read input; do
  for field in tag tag1; do
    case $input in
      *"<$field>"*"</$field>"* )
        pre=${input#*"<$field>"}
        suf=${input%"</$field>"*}
        # Where are we supposed to be getting the replacement text from?
        input="${input%$pre}SOMETHING${input#$suf}"
        ;;
    esac
  done
  echo "$input"
done

这是完全没有智能的,显然只适用于格式良好的输入,起始标签和结束标签在同一行上,您不能在同一行上有多个相同的标签,要替换的标签列表是硬编码的等等。

我无法想象实际使用此方法并优先于脚本或正确的XML方法的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接