【问题标题】:AWK get attribute value from XML elementAWK 从 XML 元素中获取属性值
【发布时间】:2021-05-07 04:13:54
【问题描述】:

大家好,我正在尝试使用 AWK 从 XML 文件中的 pkg-info 中提取 version= 值。

我只想做这样的事情:

cat file_below.txt | awk some_commands

使用下面的数据

<?xml version="1.0" encoding="utf-8"?>
<pkg-info overwrite-permissions="true" relocatable="false" identifier="com.application.something" version="1.2.3" format-version="2" generator-version="ABC" install-location="/Applications" auth="root">
</pkg-info>

期望的输出是:

1.2.3

提前谢谢你

【问题讨论】:

    标签: xml awk


    【解决方案1】:

    一种简单的方法是使用sed 定位以"&lt;pkg-info..." 开头的行,然后使用捕获版本并重新插入作为反向引用的替换来隔离版本,例如

    sed -E -n '/^<pkg-info/s/^.*[ ]version="([^"]+)".*$/\1/p' file
    

    其中-E 指定扩展正则表达式,-n 抑制模式空间的正常输出,并且:

    • /^&lt;pkg-info/ 定位以"&lt;pkg-info" 开头的行,然后是正常的
    • s/find/replace/ 替换 find 是:
    • ^.*[ ]version="([^"]+)".*$ 忽略从行首到空格后跟version=" 的字符,捕获组([^"]+) 捕获后面不是'"' 的一个或多个字符(即您想要的版本号)然后@ 987654333@ 忽略从结束 '"' 到行尾。
    • 替换为 \1,它只是插入第一个 bacreference(在上面的第一个捕获组中捕获的内容),并且
    • /p 然后打印结果。

    使用/输出示例

    file 中的示例为例,您将拥有:

    $ sed -E -n '/^<pkg-info/s/^.*[ ]version="([^"]+)".*$/\1/p' file
    1.2.3
    

    【讨论】:

    • 如果在&lt;pkg-infoversion= 之间存在\n,则中断
    • @dawg 100% 同意,解释清楚地表明/^&lt;pkg-info/ 用于定位该特定行。如果想要的版本不在以/^&lt;pkg-info/ 开头的行中,则不会返回想要的版本。
    【解决方案2】:

    对于您显示的示例,您能否尝试以下操作。用 GNU awk 编写和测试。此外,根据专家的建议,最好使用 xml 解析工具来解析 xml 文件,因为 OP 已经在使用 awk 来解析 OP 的文件,因此可以使用它。

    awk '
    /^<pkg-info/ && match($0,/[[:space:]]+version="([0-9]+\.){2}[0-9]+"[[:space:]]+/){
      val=substr($0,RSTART,RLENGTH)
      gsub(/^ +| +$/,"",val)
      print val
    }
    ' Input_file
    

    说明:为上述添加详细说明。

    awk '                             ##Starting awk program from here.
    /^<pkg-info/ && match($0,/[[:space:]]+version="([0-9]+\.){2}[0-9]+"[[:space:]]+/){
                                      ##Checking condition if line starts from <pkg-info AND matches mentioned regex.
      val=substr($0,RSTART,RLENGTH)   ##Creating val which is sub string of matched regex.
      gsub(/^ +| +$/,"",val)          ##Substituting starting and ending spaces with NULL in val.
      print val                       ##Printing val value here.
    }
    ' Input_file                      ##Mentioning Input_file name here.
    

    【讨论】:

    • 这看起来也不错,给那只猫剥皮的好方法!
    • 如果 XML 在pkg-infoversion= 之间有任何\n,则不起作用
    • @dawg,当然,这就是为什么我提到它只按照显示的示例清楚地写出来。
    【解决方案3】:

    假设标签内没有换行符

    gawk/mawk/mawk2 'BEGIN { FS = "version=\"" } /^[<]pkg-info/ {
    
        print substr($2, 1, index($2, "\"") -1 ); exit; }' 
    

    处理随机的版本\n

    gawk/mawk/mawk2 'BEGIN { FS="version=\"" } (NF > 1) { 
           
        if (seen++) { print substr($2,1,index($2, "\"")-1); exit; } }' 
    

    这将在第一次看到版本时跳过,在初始 xml 标记处。第二次打印版本号然后退出。这段代码不需要对版本号的格式做出假设,除了双引号。

    说明 pkg-info 无处不在的版本:

    gawk/mawk/mawk2 'BEGIN { RS = "^$"; FS = "([<]pkg-info|[\/]pkg-info[>])";
       
       } match($2, /version=[^ ]+/) {
    
           print substr($2, RSTART + 9, RLENGTH - 10); exit; }'
    

    只需将其读入整个 XML 文件,而不是尝试沿 NL 拆分内容。那么当你强制 FS 正好是它的开始和结束标签时,那么 $2 必须是这样一个标签的第一次出现。

    【讨论】:

    • 这实际上从第一行的&lt;?xml version="1.0" ... 输出1.0。建议awk '/&lt;pkg-info/ &amp;&amp; match($0, /version=[^ ]+/) { ...
    • 感谢 David =) 我已采纳您建议的更改
    • 好交易。像冠军一样工作。
    • 如果 XML 在pkg-infoversion= 之间有任何\n,则不起作用
    • @dawg :创建了新变体来考虑您的反馈。有用吗?
    【解决方案4】:

    你有一个 XML Element 和你希望得到的属性=值组合。

    虽然您可以有一个简单的awksed 可以从您拥有的单行示例中检索1.2.3,但您确实应该使用XML parser。如果你不这样做,它将来可能不会起作用。

    虽然您已经给出了以下属性的全单行示例:

    <?xml version="1.0" encoding="utf-8"?>
    <pkg-info overwrite-permissions="true" relocatable="false" identifier="com.application.something" version="1.2.3" format-version="2" generator-version="ABC" install-location="/Applications" auth="root">
    </pkg-info>
    

    同样的数据也可以:

    <?xml version="1.0" encoding="utf-8"?>
    <pkg-info overwrite-permissions="true" 
              relocatable="false" identifier="com.application.something" 
              version="1.2.3" format-version="2" 
              generator-version="ABC" install-location="/Applications" auth="root">
    </pkg-info>
    

    或者,

    <?xml version="1.0" encoding="utf-8"?><pkg-info overwrite-permissions="true" relocatable="false" identifier="com.application.something" version="1.2.3" format-version="2" generator-version="ABC" install-location="/Applications"  auth="root"/>
    

    并且仍然被解析为相同的数据。所有三个示例都是有效的 XML,但这里的 awk 或 sed 答案都不能处理除第一个示例之外的任何内容。

    对于 XML,'\n'' ''\t''\r' 都是相同的1 但是对于 awk 和 sed,这些字符非常不同的意思。尝试强制使用 awk 或 sed 等面向行的工具来处理 XML 等面向标签的数据非常脆弱

    处理此问题的最佳方法是使用XPath 查询。相关查询是:

    /pkg-info/@version
    

    DEMO

    鉴于file 具有上述某种有效形式的 XML,您可以使用其中一种方法。

    这是一个简单的 Ruby 示例。使用nokogiri xml 解析器通过 xpath 解析感兴趣的属性:

    ruby -r nokogiri -e 'doc=Nokogiri::XML($<.read)
    puts doc.xpath("/pkg-info").attribute("version").value' file
    1.2.3
    

    (您可能需要在系统上安装带有gem install nokogiri 的 nokogiri...)

    或者XMLStarlet:

    xml sel -t -v '/pkg-info/@version' file
    1.2.3
    

    如果您在 Perl 中安装了 XML::XPath 模块(大多数系统都安装了),那么您还有一个名为 xpath 的命令行 XPath 查询工具。你可以这样做:

    xpath -q -e '/pkg-info/@version' file
     version="1.2.3"
    

    然后通过sed 运行它以获取值:

    xpath -q -e '/pkg-info/@version' file | sed -E 's/[^"]*"([^"]*).*/\1/'
    1.2.3 
    

    请注意,XML 解析器可以处理任何合法版本的 XML 数据。这里的其他sedawk 解决方案不会。


    如果您的真的非常非常想要使用正则表达式,Perl 是一个更好的选择。这适用于上述所有三个示例:

    perl -0777 -lnE 'say $1 if /(?:\s|>)<pkg-info[\s\S]*?\sversion="([^"]+)"/m' file
    

    如果你hafta hafta hafta有一个awk,你可以设置RS-"^$",它具有将整个文件作为一个字符串读取的效果:

    1. 使用"&lt;pkg-info " 查找要点。
    2. 由于这些是属性而不是嵌套标签,因此属性部分中不会有&gt;。但是,无论&lt;pkg-info 元素如何终止,都必须有一个&gt; 来终止它。
    3. 现在将' version=" 值两侧的所有内容替换为""
    4. 印刷和盈利。

    这个awk 适用于我的所有示例;但是,您确实应该使用 XML 解析器。

    awk -v RS="^$" '{ x=index($0, "<pkg-info ")
                      s=substr($0,x)
                      sub(/[^>]*\sversion="/,"", s)
                      sub(/".*/,"", s)
                      print s
                    }' file
    

    1只要这些字符是insignificant whitespace,在本例中就是...

    【讨论】:

      【解决方案5】:

      Awk 和 XML 并不是最好的朋友,因为 awk 是一个正则表达式驱动的基于行的工具。 XML 不是一种可以使用基于行的工具轻松过滤的简单格式;同样,很难创建一个能够可靠地匹配 XML 可以呈现的所有方式的正则表达式。

      为了确保我们不会犯错,我们利用理解 XML 的状态机(过滤器)将其转换为我们可以可靠使用的基于行的东西。 xml2 就是一个这样的工具,它提供了来自 XML 的可解析的“平面”输出。这是您的样本过滤结果的示例....

      $ xml2 < some.xml
      /pkg-info/@overwrite-permissions=true
      /pkg-info/@relocatable=false
      /pkg-info/@identifier=com.application.something
      /pkg-info/@version=1.2.3
      /pkg-info/@format-version=2
      /pkg-info/@generator-version=ABC
      /pkg-info/@install-location=/Applications
      /pkg-info/@auth=root
      

      过滤 XML 之后,创建一个可靠的 awksed 过滤器来获取我们的输出是很简单的......这里有几个想法:

      $ xml2 < some.xml | awk -F= '$1 == "/pkg-info/@version" { print $2 }'
      1.2.3
      $ xml2 < some.xml | sed -e 's,^/pkg-info/@version=,,; t; d'
      1.2.3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-01-10
        • 1970-01-01
        • 1970-01-01
        • 2011-05-07
        • 1970-01-01
        • 2021-08-26
        • 1970-01-01
        • 2021-07-16
        相关资源
        最近更新 更多