2016-06-23 2 views
0
XML

У меня есть плоский файл, как это:Shell скрипт для чтения плоского файла и заменить значения

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 и т. д.) для чтения плоского файла и замены всех данных в тегах <value> в xml с данными, найденными после ~, когда тег <name> соответствует данным до ~. В конечном итоге получившийся XML будет выглядеть так:

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

Спасибо за вашу помощь!

+0

Кстати, ваш «XML как это» на самом деле недостаточно хорош для проверки правильности. Заголовок имеет важное значение: если обрабатываемый XML-файл начинается с '', это означает что-то совершенно иное, чем если оно только начиналось с ''. –

+0

ocbit: 'sed',' awk' и т. Д.не может справиться с XML надежно - синтаксис * совсем не * контекстно-свободный, то есть вам нужно отслеживать, какие атрибуты xmlns были замечены в предыдущих тегах, независимо от того, находитесь ли вы в комментарии, независимо от того, в разделе CDATA и т. д. решить, что делать в любой момент времени. (И это прежде чем иметь дело с вещами, такими как расширение сущностей, или значениями, которые нужно избегать, чтобы не нарушать синтаксис). См. Также соответствующий http://stackoverflow.com/a/1732454/14122 –

ответ

4

Использование 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 не защищает от этого; однако использование переменных связывания делает возможным для реализации, чтобы обеспечить такие меры предосторожности, поэтому будущий выпуск может быть безопасным в этом контексте.

1

Использование Perl и XML::XSH2, обертку XML::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 в <root> тег, чтобы сделать его хорошо сформированы.

+0

Bravo для реальных решений на основе XML-парсеров. :) –

+0

@choroba спасибо за помощь, но моя оболочка не поддерживает XML :: XSH2 - Невозможно найти XML/XSH2.pm в INC. Любой шанс, что этот скрипт можно написать, используя чисто awk или sed, даже если он не может быть надежным? У моего xml будут только пары имя/значение, как в моем примере. – ocbit

+0

@ocbit: вы можете установить его через 'cpan XML :: XSH2'. – choroba