使用Shell脚本获取多行XML中的标签值

时间:2019-05-30 06:52:41

标签: linux bash shell

我有一个xml文件,如下所示

<Module dataPath="/abc/def/xyz" handler="DataRegistry" id="id1" path="test.so"/>
<Module id="id2" path="/my/file/path">
  <Config>
    <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/xyz" />
    <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/xyz" id="V2"/>
  </Config>
</Module>

我只想从每个dataPath中提取moduleid的值。

我正在使用

之类的命令
`grep 'id2' file | grep -ioPm1 "(?<=DataPath=)[^ ]+"`

这是从第一个模块ID给我的,而不是从第二个模块ID给我的。因为第二个模块在多行中。

如何使用Shell脚本执行此操作?

所需的输出将是–如果我想获取id1模块的数据路径,则应该获取

/my/file/path

对于第二个模块id,例如id2,我应该用逗号分隔数据路径

/my/file/path, /my/file/path

或者,我第二种grep数据路径的方法是只在newline character<Module之间替换</Module>,那么我可以使用grep。

2 个答案:

答案 0 :(得分:2)

-m1告诉grep在第一行匹配行后退出,这就是为什么它仅输出一行输出的原因。 我不会为此使用面向行的工具。有更方便的工具来解析XML,例如

xml sel -t -m '//@dataPath' -v . -n file.xml

答案 1 :(得分:1)

首先,我的答案假设您具有实际格式正确的源XML。您提供的示例代码没有根元素-但我仍然假设存在一个根元素。

Bash功能本身并不十分适合解析XML。

此著名的Bash FAQ指出:

  

请勿尝试使用等[从XML文件中提取数据](以{ 3}})

如果必须使用Shell脚本,则使用XML特定的命令行工具,例如undesired resultsXMLStarlet。如果尚未安装XML Starlet,请参阅下载信息xsltproc


解决方案:

  1. 给出您的源XML和所需的输出,请考虑使用以下here模板来实现此目的。

    template.xsl

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:output method="text"/>
    
      <xsl:template match="node()|@*">
        <xsl:apply-templates select="node()|@*"/>
      </xsl:template>
    
      <xsl:template match="Module">
        <xsl:choose>
    
          <xsl:when test="@dataPath and not(descendant::*/@dataPath)">
            <xsl:value-of select="@dataPath"/>
            <xsl:text>&#xa;</xsl:text>
          </xsl:when>
    
          <xsl:when test="not(@dataPath) and descendant::*/@dataPath">
            <xsl:for-each select="descendant::*/@dataPath">
              <xsl:value-of select="."/>
              <xsl:if test="position()!=last()">
                <xsl:text>, </xsl:text>
              </xsl:if>
            </xsl:for-each>
            <xsl:text>&#xa;</xsl:text>
          </xsl:when>
    
          <xsl:when test="@dataPath and descendant::*/@dataPath">
            <xsl:value-of select="@dataPath"/>
            <xsl:text>, </xsl:text>
            <xsl:for-each select="descendant::*/@dataPath">
              <xsl:value-of select="."/>
              <xsl:if test="position()!=last()">
                <xsl:text>, </xsl:text>
              </xsl:if>
            </xsl:for-each>
            <xsl:text>&#xa;</xsl:text>
          </xsl:when>
    
        </xsl:choose>
      </xsl:template>
    
    </xsl:stylesheet>
    
  2. 然后运行;

    • 以下XML Starlet命令:

      $ xml tr /path/to/template.xsl /path/to/input.xml
      
    • 或以下xsltproc命令:

      $ xsltproc /path/to/template.xsl /path/to/input.xml
      

    注意:应该将上述命令中template.xslinput.xml的路径名重新定义为这些文件所在的位置。 < / p>

    上面的两个命令本质上都会转换您的input.xml文件并输出所需的结果。


演示:

  1. 使用以下input.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <root>
      <Module dataPath="/abc/def/1" handler="DataRegistry" id="id1" path="test.so"/>
    
      <Module id="id2" path="/my/file/path">
        <Config>
          <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/2" />
          <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/3" id="V2"/>
        </Config>
      </Module>
    
      <Module id="id3" path="/my/file/path" dataPath="/abc/def/4">
        <Config>
          <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/5" />
          <Source cutoffpackage="1" dailyStart="20060819" dataPath="/abc/def/6" id="V2"/>
        </Config>
      </Module>
    
      <Module id="id4" path="/my/file/path" dataPath="/abc/def/7"/>
      <Module id="id5" path="/my/file/path" dataPath="/abc/def/8"/>
    
    
      <!-- The following <Module>'s have no associated `dataPath` attribute -->
      <Module id="id6">
        <Config>
          <Source cutoffpackage="1" dailyStart="20060819" id="V2"/>
        </Config>
      </Module>
    
      <Module id="id7"/>
    </root>
    
  2. 然后运行上述任何一个命令,都会显示以下结果:

    /abc/def/1
    /abc/def/2, /abc/def/3
    /abc/def/4, /abc/def/5, /abc/def/6
    /abc/def/7
    /abc/def/8
    

附加说明:

如果您想避免使用单独的.xsl文件,则可以按以下步骤在shell脚本中内联上述XSLT模板:

script.sh

#!/usr/bin/env bash

xslt() {
cat <<EOX
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>

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

  <xsl:template match="Module">
    <xsl:choose>

      <xsl:when test="@dataPath and not(descendant::*/@dataPath)">
        <xsl:value-of select="@dataPath"/>
        <xsl:text>&#xa;</xsl:text>
      </xsl:when>

      <xsl:when test="not(@dataPath) and descendant::*/@dataPath">
        <xsl:for-each select="descendant::*/@dataPath">
          <xsl:value-of select="."/>
          <xsl:if test="position()!=last()">
            <xsl:text>, </xsl:text>
          </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xa;</xsl:text>
      </xsl:when>

      <xsl:when test="@dataPath and descendant::*/@dataPath">
        <xsl:value-of select="@dataPath"/>
        <xsl:text>, </xsl:text>
        <xsl:for-each select="descendant::*/@dataPath">
          <xsl:value-of select="."/>
          <xsl:if test="position()!=last()">
            <xsl:text>, </xsl:text>
          </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xa;</xsl:text>
      </xsl:when>

    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>
EOX
}

# 1. Using XML Startlet
xml tr <(xslt) /path/to/input.xml

# 2. Or using xsltproc
xsltproc <(xslt) - </path/to/input.xml

注意:您的input.xml的路径名(即上面/path/to/input.xml中的script.sh部分)应重新定义为文件驻留。