2015-10-20 3 views
0

Мне нужно обработать набор файлов XML в текущем каталоге, используя sed или аналогичную утилиту из сценария bash.bash scripting: как заменить эти строки, соответствующие шаблону другой строкой?

В каждом файле, который имеет одну из следующих строк (там может быть 0 или 1 из них в файле)

<MetaDatum key="Pr" value="VALUE (foobar)" /> 
    <MetaDatum key="Pr" value="VALUE (xyz12345678)" /> 

Мне нужно заменить, что вся линия с

<MetaDatum key="Pr" value="VALUE" /> 

Так мне нужно сделать по существу карту VALUE (foobar) и VALUE (xyz12345678) в VALUE.

Так что операция следует использовать внутри этого цикла:

for f in `grep -l "MetaDatum key=\"Pr\" value=\"VALUE" *.xml` 
do 
    # replace one entire line in $f with '<MetaDatum key="Pr" value="VALUE" />' 
done 
+0

Насколько большая часть этой строки уникальна для строк, которые вы хотите отредактировать? Достаточно ли '

+0

@EtanReisner: 'grep -l' возвращает файлы. – choroba

+0

Да, спасибо, забыли '-l'. Остальной части вашего q: '

ответ

1

Предположив, что картина в вашей команде grep идентифицирует все строки, которые должны быть изменены и никаких других строки, вы могли бы написать sed команды, которая соответствует той же схеме, и подставляется значение атрибута value на нем:

sed '/MetaDatum key="Pr" value="VALUE/ s/value="[^"]*"/value="VALUE"/' $f 

Заметим, однако, что такой подход (как grep и sed) очень чувствительны к точным деталям вашего XML. Он будет падать на разное количество пробелов, чем вы ожидаете - особенно встроенные новые строки - на дополнительные атрибуты, на разные варианты котировок и т. Д.

Некоторые из них могут быть решены более умными шаблонами, а другие нет. Чтобы правильно обрабатывать XML, вам нужны надежные инструменты XML. В этом случае подходящим инструментом будет преобразование XSLT. Вот преобразование, которое будет выполнять работу (при условии, что исходный файл не переопределены по умолчанию XML пространство имен - спасибо, CharlesDuffy):

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <!-- identity transform: anything not otherwise matched is copied verbatim --> 
    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 

    <!-- 
    -- Transform the 'value' attribute of MetaDatum elements where the 
    -- element has a 'key' attribute with value 'Pr', and the 'value' 
    -- attribute's own value starts with 'VALUE'. 
    --> 
    <xsl:template match="MetaDatum[@key = 'Pr']/@value[substring(., 1, 5) = 'VALUE']"> 
    <xsl:attribute name="value">VALUE</xsl:attribute> 
    </xsl:template> 

</xsl:stylesheet> 

Вы можете применять его через любой XSLT процессор, но один из наиболее распространенных из них xsltproc, который поставляется с libxslt от GNOME. Если преобразование хранится в файле meta.xsl, то команды, чтобы заменить файл $f с преобразованным выходом через xsltproc может быть:

temp=`mktemp` && xsltproc meta.xsl "$f" > "$temp" && mv "$temp" "$f" 

Как @CharlesDuffy отмечен в комментариях, что может привести к файлу с именем по $f, имеющим разным права собственности и/или более ограничительные разрешения, чем это было ранее. Как вы можете решить эту проблему, зависит от доступных инструментов. Например, хотя стандартные chown и chmod не имеют его, версии GNU имеют механизмы для настройки права собственности и разрешений файла в соответствии с файлами другого файла. Кроме того, вам нужно подумать о том, какое желаемое поведение относится к случаю, когда $f называет символическую ссылку (замените ссылку или измените файл, на который она указывает). Поскольку эти вопросы, зависящие от среды и предпочтения, если команда, представленная выше, не обрабатывает их по своему усмотрению, тогда вам нужно будет решить, как пересмотреть подход.

Если вам нужно иметь дело с переопределенным пространством имен XML по умолчанию, тогда шаблон должен быть немного сложнее. Вам нужно будет объявить префикс пространства имен для пространства имен элемента MetaDatum и его атрибутов и использовать его везде, где вы ссылаетесь на эти имена.

+0

Отлично. Только случай, который не рассматривается здесь, заключается в том, что фактический файл OP имеет переопределенное пространство имен по умолчанию. –

+0

Одно из предостережений с одним слоем, кстати, это потенциально делает разрешения значительно более ограничительными (поскольку 'mktemp' использует консервативный umask). Если это GNU-система, вы можете использовать 'chown -reference = '$ f" ',' chmod -reference = "$ f" 'и т. Д., Чтобы скопировать права собственности и разрешения. –

+0

@CharlesDuffy, все хорошие моменты. Я пересмотрел свой ответ, чтобы обсудить их, хотя я не изменял таблицу стилей или однострочный шрифт. Я думаю, что разумно позволить OP самостоятельно адаптировать ответ к его конкретным потребностям, независимо от того, что требуется. –

2

Вместо sed, использовать инструмент, который должным образом разбирает XML. Например, в xsh вы можете использовать

for $file in { glob '*.xml' } { 
    open $file ; 
    for //MetaDatum/@value 
     set . xsh:subst(., 'VALUE \(.*', 'VALUE') ; 
    save :b ; 
} 
0

Используйте эту СЭД команду в одну строчку, чтобы изменить все файлы XML (в текущем каталоге) на месте:

sed -i 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' *.xml 

Вы также можете сделать резервную копию предыдущей версии, добавляя что-то после переключения -i , такие как .bak суффикса (или ~ если:

sed -i.bak 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' *.xml 

Объединить команду с find инструментом для применения СЭДА к файлам с расширением .xml (регистронезависимый), которые можно найти в том rget или ее подпапки:

find ${targetDir} -type f -iname "*.xml" -exec sed -i 's,\(<MetaDatum\s*key="Pr"\s*value="VALUE\).*\s*/>,\1" />,' {} \; 
1

Вы не можете надежно использовать sed для этой работы: XML может быть записан слишком много различных способов. (Например, ваш документ может иметь атрибуты ключа и значения в разных строках от значения, к которому они применяются, или может поставить «значение» перед «ключом» или может начать использовать именованные пространства имен и, таким образом, добавляет foo: префиксы к вещам). Нет никакой гарантии, что будущие версии вашего входного файла будут сгенерированы с точно таким же форматированием, особенно в случае изменения кода, который его генерирует.

Вместо этого используйте инструмент XML-Aware, такие как XMLStarlet:

xmlstarlet ed \ 
    -u '//MetaDatum[@key="Pr"]/@value' \ 
    -v "VALUE" \ 
    <in.xml >out.xml 

Обратите внимание, что если есть xmlns="..." декларация на области видимости в файле, то это изменит выражение выше немного. (Это также означает, что ваш формат файла использует пространства имен, поэтому, скорее всего, изменится так, как sed не справится!)

Например, если в верхней части файла начинается что-то вроде <root xmlns="http://example.com/foo">, тогда вам понадобится сделать следующее:

xmlstarlet ed \ 
    -N "foo=http://example.com/foo" 
    -u '//foo:MetaDatum[@key="Pr"]/@value' \ 
    -v "VALUE" \ 
    <in.xml >out.xml 

Кстати - если вы не хотите выполнять изменения в месте, xmlstarlet ed имеет -i возможность вносить изменения в линию; таким образом: xmlstarlet ed -i [...] changeme.xml будет выписывать модифицированную версию changeme.xml, позволяя использовать однострочные линии с одним слоем, показанные некоторыми другими ответами.

Смежные вопросы