Вопрос или проблема
Я использую Linux и хочу использовать sed
, чтобы удалить косую черту (/
) между 2 тегами.
Из этого: input.xml
…
<file>/text</file>
<file>/text2</file>
<file>/text</file>
… в это: output.xml
:
<file>text</file>
<file>text2</file>
<file>text</file>
Тестировал много кода без успеха
пример:
sed s'/<file>/s|^\.{1,2}/||' fileout
Можете помочь?
Имея корректный XML-файл, такой как
<?xml version="1.0"?>
<root>
<file>/text</file>
<file>/text2</file>
<file>/text</file>
<file>другой текст</file>
</root>
… вы можете использовать XMLStarlet, чтобы удалить первый символ значения каждого узла file
, если значение начинается с /
:
xmlstarlet edit \
--update '//file[starts-with(text(), "/")]' \
--expr 'substring(text(), 2)' \
myfile.xml
Или, используя более короткий синтаксис,
xmlstarlet ed \
-u '//file[starts-with(text(), "/")]' \
-x 'substring(text(), 2)' \
myfile.xml
Это находит каждый узел file
во всем входном документе, значение которого начинается с /
, а затем удаляет этот первый символ, используя substring()
.
Результат:
<?xml version="1.0"?>
<root>
<file>text</file>
<file>text2</file>
<file>text</file>
<file>другой текст</file>
</root>
Это (и ниже) будет работать с узлами, значения которых содержат встроенные переносы строк.
Если вы хотите обнаружить /
в любом месте значения, а не только в начале, и если вы хотите удалить их все, вы можете сделать это, используя contains()
и translate()
:
xmlstarlet edit \
--update '//file[contains(text(), "/")]' \
--expr 'translate(text(), "/", "")' \
myfile.xml
Или просто (поскольку вызов translate()
оставит значения без изменений, если в них не было /
),
xmlstarlet edit \
--update '//file' \
--expr 'translate(text(), "/", "")' \
myfile.xml
Учитывая этот входной файл:
<?xml version="1.0"?>
<root>
<file>text/</file>
<file>/text/2</file>
<file>te/x/t/</file>
<file>другой текст</file>
</root>
… вышеуказанная команда приведет к следующему:
<?xml version="1.0"?>
<root>
<file>text</file>
<file>text2</file>
<file>text</file>
<file>другой текст</file>
</root>
Входной (исправленный синтаксис) XML
файл (пропущен закрывающий >
на втором узле file
):
<r>
<file>/text</file>
<file>/text2</file>
<file>/text</file>
</r>
С современным синтаксисом и правильной функцией XPath
fn:replace()
(что-то вроде sed
для XPath
, позволяя использовать регулярные выражения и группы захвата с XPath
версии >= 2), используя XQuery
, вы можете сделать следующее:
xidel --xquery '
<r>{
for $x in //file
return <file>{replace($x, "^/(.*)", "$1")}</file>
}</r>
' --output-format=xml file.xml
Это даст:
<?xml version="1.0" encoding="UTF-8"?>
<r>
<file>text</file>
<file>text2</file>
<file>text</file>
</r>
Если вам нужно редактировать файл на лету, используйте sponge
, утилиту из GNU
more-utils
:
xidel ... file.xml | sponge file.xml
- обзор регулярных выражений в
XPath/XQuery
https://www.regular-expressions.info/xpath.html XPath
является подмножествомXQuery
. Смотрите разницу между xpath, xquery и xpointerxidel
– это швейцарский армейский нож для манипуляции HTML/XML.- вы также можете использовать процессор
XQuery
(с открытым исходным кодом)BaseX
для выполнения выраженийXQuery
Используя Raku (ранее известный как Perl_6)
…с поддерживаемым сообществом модулем XML
для Raku:
~$ raku -MXML -e 'my $xml = open-xml( $*ARGFILES.Str );
for $xml.elements( :RECURSE(0), :TAG{"file"} ) -> $E {
my $old = $E.contents[0];
my $new = XML::Text.new( text => $old.text.subst(/^ "/" /) );
$E.replace( $old, $new );
}; .say for $xml;' file.xml
Raku – это язык программирования в семье Perl, который предлагает высокоуровневые грамматики для разбора текста. В приведенном выше примере используется его родной XML-Grammar движок, модуль XML
в Raku разбирает входной XML
файл. Таким образом, XML-элементы идентифицируются и могут быть перебраны.
Проблема решений только с использованием регулярных выражений (например, sed
) для XML
заключается в том, что замены tend tend дико: вы часто сталкиваетесь с трудностями в ограничении замен только определенными уровнями/тегами. В Raku с модулем XML
вы можете, например, ограничить замены 1). верхним уровнем и 2). только внутри тега <file>
. Это делается путем установки кода на итерацию через elements
с ограничениями :RECURSE(0), :TAG{"file"}
. Здесь также можно добавить :NEST
, чтобы перебрать только EVEN
узлы.
[ Не беспокойтесь, если вы хотите перебрать все TAG
на любых уровнях: просто установите :RECURSE(Inf)
и удалите именованный аргумент :TAG
, который устанавливает ограничение :TAG
на ложь ].
Таким образом, каждый внутренний (т.е. не теговый) contents[0]
элемента назначается переменной $old
, которая на самом деле является объектом XML::Text
. Объект $old
извлекается в строку через .text
, и subst
заменяется на пустую строку, чтобы удалить нежелательную косую черту "/"
. Создается новый (XML::Text.new
) объект ($new
) с исправленным text => 'value'
парой ключ/значение. На этом этапе процедура replace
модуля XML
завершает работу: replace( $old, $new )
.
Пример входных данных (спасибо @Kusalananda!):
<?xml version="1.0"?>
<root>
<file>/text</file>
<file>/text2</file>
<file>/text</file>
<file>другой текст</file>
</root>
Пример выходных данных (ведущая /
удалена из тегов <file>):
<?xml version="1.0"?><root>
<file>text</file>
<file>text2</file>
<file>text</file>
<file>другой текст</file>
</root>
https://github.com/raku-community-modules/XML
https://raku.land/?q=XML
https://rakudo.org/
https://raku.org
Ответ или решение
Для решения задачи удаления символа косой черты (/
) между тегами <file>
в XML-файле с помощью утилиты sed
в Linux, необходимо использовать соответствующий синтаксис для замены.
Исходя из вашего примера, содержащего следующие строки:
<file>/text</file>
<file>/text2</file>
<file>/text</file>
вы хотите получить:
<file>text</file>
<file>text2</file>
<file>text</file>
Для этого вы можете использовать команду sed
, которая позволяет заменять текст по заданным паттернам. Ваша команда sed
должна выглядеть следующим образом:
sed 's|<file>/\([^<]*\)</file>|<file>\1</file>|g' input.xml > output.xml
Объяснение команды:
- s|…|…|g – это основной шаблон замены, где
s
обозначает "замена", аg
в конце означает "глобальная замена" (которая затрагивает все вхождения в строке). - *
/([^<] )** – это регулярное выражение, которое находит текст между тегами<file>
, начинающийся с косой черты. Вот что оно означает:<file>/
– ищем вхождение<file>/
.\([^<]*\)
– захватываем (группируем) все символы, кроме<
, следящие за/
. Это значение будет подставлено в результат замены. Скобки обозначают группу.</file>
– ищем конец тега</file>
.
\1 – здесь мы заменяем найденный шаблон на<file>
и содержимое группы\1
, которая содержит текст без начальной косой черты, и закрывающий тег</file>
.
Пример использования:
Если у вас есть файл input.xml
со следующим содержимым:
<root>
<file>/text</file>
<file>/text2</file>
<file>/text</file>
<file>other text</file>
</root>
Вы можете выполнить команду:
sed 's|<file>/\([^<]*\)</file>|<file>\1</file>|g' input.xml > output.xml
После выполнения этой команды файл output.xml
будет содержать:
<root>
<file>text</file>
<file>text2</file>
<file>text</file>
<file>other text</file>
</root>
Заключение
Таким образом, использование sed
позволяет эффективно удалять символы /
из содержимого между тегами в XML-документе. Обратите внимание, что регулярные выражения в sed
могут быть чувствительными к изменениям и интерпретации, так что всегда полезно протестировать команду на небольшом наборе данных перед применением.