【问题标题】:Shell script to read flat file and replace xml values用于读取平面文件并替换 xml 值的 Shell 脚本
【发布时间】:2016-10-26 09:07:18
【问题描述】:

我有一个这样的平面文件:

File: 
# Environment
Application.Env~DEV
# Identity
Application.ID~999
# Name
Application.Name~appname

这样的 XML:

<name>Application/Env</name>
<value>XXX</value>
<name>Application/ID</name>
<value>000</value>
<name>Application/Name</name>
<value>AAA</value>

我正在寻找一个脚本(awk、sed 等)来读取平面文件并将 xml 中 &lt;value&gt; 标记中的所有数据替换为 &lt;name&gt; 标记匹配时在 ~ 之后找到的数据~之前的数据。最终生成的 XML 将如下所示:

    <name>Application/Env</name>
    <value>DEV</value>
    <name>Application/ID</name>
    <value>999</value>
    <name>Application/Name</name>
    <value>appname</value>

感谢您的帮助!

【问题讨论】:

  • 顺便说一句,您的“像这样的 XML”实际上并不足以验证其正确性。标头很重要——如果您正在处理的 XML 文件以 &lt;root xmlns="foo"&gt; 开头,这意味着与刚刚以 &lt;root&gt; 开头的情况完全不同。
  • ocbit: sedawk 等无法可靠地处理 XML -- 语法根本不是上下文无关的,这意味着您需要跟踪在之前的标签中看到了哪些xmlns 属性,无论您是否在评论中,您是否在 CDATA 部分中,等等。决定在任何给定时间做什么。 (这是在处理实体扩展或需要转义以避免破坏语法的值之前)。另见相关stackoverflow.com/a/1732454/14122

标签: shell awk sed ksh


【解决方案1】:

使用XMLStarlet,这将如下所示:

#!/bin/bash

# usage: [script] [flatfile-name] <in.xml >out.xml
flatfile=$1

# store an array of variables, and an array of edit commands
xml_vars=( )
xml_cmd=( )
count=0

while read -r line; do
  [[ $line = *"~"* ]] || continue
  key=${line%%"~"*}   # put everything before the ~ into key
  key=${key//"."/"/"} # change "."s to "/"s in key
  val=${line#*"~"}    # put everything after the ~ into val

  # assign key to an XMLStarlet variable to avoid practices that can lead to injection
  xml_vars+=( --var "var$count" "'$key'" )

  # update the first value following a matching name
  xml_cmd+=( -u "//name[.=\$var${count}]/following-sibling::value[1]" \
             -v "$val" )

  # increment the counter used to assign variable names
  (( ++count ))
done <"$flatfile"

if (( ${#xml_cmd[@]} )); then
  xmlstarlet ed "${xml_vars[@]}" "${xml_cmd[@]}"
else
  cat # no edits to do
fi

这将运行如下命令:

xmlstarlet ed \
  --var var0 "Application/Env" \
  --var var2 "Application/ID"  \
  --var var3 "Application/Name" \
  -u '//name[.=$var0]/following-sibling::value[1]' -v 'DEV' \
  -u '//name[.=$var1]/following-sibling::value[1]' -v '999' \
  -u '//name[.=$var2]/following-sibling::value[1]' -v 'appname'

...它将名称 Application/Env 后的第一个值替换为 DEV,名称 Application/ID 后的第一个值替换为 999,并将名称 Application/Name 后的第一个值替换为 appname .


一种稍微不那么偏执的方法可能会生成像//name[.="Application/Name"]/following-sibling::value[1] 这样的查询;将变量带外作为一种安全做法正在被遵循。考虑一下如果输入文件包含以下内容会发生什么:

Application.Foo"or 1=1 or .="~bar

...生成的 XPath 是

//name[.="Application/Foo" or 1=1 or .=""]/following-sibling::value[1]

因为1=1 始终为真,这将匹配每个 名称,从而将文件中的每个 值更改为bar

不幸的是,XMLStarlet 的实现并不能有效地防止这种情况;然而,使用绑定变量使得实现有可能提供这样的预防措施,因此未来的版本在这种情况下可能是安全的。

【讨论】:

    【解决方案2】:

    使用 Perl 和 XML::XSH2XML::LibXML 的包装器:

    #!/usr/bin/perl
    use warnings;
    use strict;
    use XML::XSH2;
    
    open my $IN, '<', 'flatfile' or die $!;
    $XML::XSH2::Map::replace = { map { chomp; split /~/ } grep /~/, <$IN> };
    
    xsh << 'end.';
        open 1.xml ;
        for //name {
            set following-sibling::value[1]
                xsh:lookup('replace', xsh:subst(., '/', '.')) ;
        }
        save :b ;
    end.
    

    我将 XML 包装到 &lt;root&gt; 标记中以使其格式正确。

    【讨论】:

    • Bravo 真正基于 XML 解析器的解决方案。 :)
    • @choroba 感谢您的帮助,但我的 shell 不支持 XML::XSH2 - 无法在 INC 中找到 XML/XSH2.pm。任何机会都可以使用纯粹的 awk 或 sed 编写此脚本即使它可能不可靠?我的 xml 将只有我的示例中的名称/值对。
    • @ocbit: 你应该可以通过cpan XML::XSH2安装它。
    • @choroba:很遗憾,因为这是一台工作计算机,所以没有机会。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-16
    • 1970-01-01
    • 2016-04-14
    • 1970-01-01
    • 2015-11-04
    相关资源
    最近更新 更多