Вопрос или проблема
У меня есть много XML-файлов, в которых я хотел бы заменить одну строку на новую. Я не могу заставить команду sed работать с XML-файлами.
<form version="1.1" theme="dark">
<label>Панель управления DLP Forcepoint - Долгосрочные исключения</label>
<description>Деятельность для тех, у кого есть долгосрочные исключения</description>
<fieldset submitButton="false" autoRun="false">
<input type="time" token="TimeFrame" searchWhenChanged="true">
<label>Временной интервал</label>
<default>
<earliest>-48h@h</earliest>
<latest>сейчас</latest>
</default>
</input>
</fieldset>
<row>
<panel>
<html>
<p>Используемые макросы:</p>
<p>`ForcepointApprovedUSB` = Известные одобренные USB-устройства</p>
<p>`ForcepointKnownCDDVD` = Известные приводы CD/DVD</p>
<p>`ForcepointKnownMultiFunction` = Известные многофункциональные устройства</p>
</html>
</panel>
</row>
<row>
<panel>
<title>Информация о исключении</title>
<table>
<search>
<query>index=restricted_security
sourcetype=forcepoint
| rex field=_raw "(.*act=(?<Action>.*?)\s.*)"
| rex field=_raw "(.*duser=(?<Device>.*?)(:\s\d|;|\sfname=).*)"
| rex field=_raw "(.*duser=.*?;\s(?<Serial>.*?)\sfname=)"
| rex field=_raw "(.*fname=(?<Filename>.*?)\smsg=.*)"
| rex field=_raw "(.*fname=.:\\\(?<RawFilename>.*)(?:\s-\s.*)\smsg=.*)"
| rex field=_raw "(.*suser=(?<Name>.*)\scat=.*)"
| rex field=_raw "(.*loginName=.*\\\\(?<Username>.*)\ssourceIp=.*)"
| rex field=_raw "(.*sourceIp=(?<IP>.*)\sseverityType=.*)"
| rex field=_raw "(.*sourceHost=(?<Source>.*)\sproductVersion=.*)"
| rex field=_raw "(.*sourceServiceName=(?<AlertType>.*)\sanalyzedBy=.*)"
| eval Username=lower(Username)
| eval Action=if(isnull(Action),"-",Action)
| eval Serial=if(isnull(Serial),"-",Serial)
| eval EnumDeviceType=case(
(`ForcepointApprovedUSB`),"ApprovedUSB",
(`ForcepointKnownCDDVD`),"CDDVD",
(`ForcepointKnownMultiFunction`),"MultiFunction",
AlertType="Endpoint Applications" AND Device="Bluetooth","Bluetooth",
AlertType="Endpoint Removable Media" AND Device="Windows Portable Device (WPD)","WPD",
AlertType="Endpoint Removable Media" AND
Device!="Windows Portable Device (WPD)" AND NOT
(`ForcepointApprovedUSB`) AND NOT
(`ForcepointKnownCDDVD`) AND NOT
(`ForcepointKnownMultiFunction`),"UnApprovedUSB")
| join type=inner Username
[
search
index=restricted_security
sourcetype=dlp_lt
| rename UserID as Username
| eval Check = "Yes"
| fields Username,Check,Justification,Type,ExpireDate
]
| where isnotnull(EnumDeviceType) AND Check="Yes"
| eval Time=strftime(_time, "%B %d, %Y %H:%M %Z")
| dedup Username
| table Time Username Name Justification Type ExpireDate
| sort Name</query>
<earliest>$TimeFrame.earliest$</earliest>
<latest>$TimeFrame.latest$</latest>
</search>
<option name="drilldown">none</option>
<option name="refresh.display">progressbar</option>
</table>
</panel>
</row>
<row>
<panel>
<title>Передачи от тех, у кого есть долгосрочные исключения</title>
<table>
<search>
<query>index=restricted_security
sourcetype=forcepoint
| rex field=_raw "(.*act=(?<Action>.*?)\s.*)"
| rex field=_raw "(.*duser=(?<Device>.*?)(:\s\d|;|\sfname=).*)"
| rex field=_raw "(.*duser=.*?;\s(?<Serial>.*?)\sfname=)"
| rex field=_raw "(.*fname=(?<Filename>.*?)\smsg=.*)"
| rex field=_raw "(.*fname=.:\\\(?<RawFilename>.*)(?:\s-\s.*)\smsg=.*)"
| rex field=_raw "(.*suser=(?<Name>.*)\scat=.*)"
| rex field=_raw "(.*loginName=.*\\\\(?<Username>.*)\ssourceIp=.*)"
| rex field=_raw "(.*sourceIp=(?<IP>.*)\sseverityType=.*)"
| rex field=_raw "(.*sourceHost=(?<Source>.*)\sproductVersion=.*)"
| rex field=_raw "(.*sourceServiceName=(?<AlertType>.*)\sanalyzedBy=.*)"
| eval Username=lower(Username)
| eval Action=if(isnull(Action),"-",Action)
| eval Serial=if(isnull(Serial),"-",Serial)
| eval EnumDeviceType=case(
(`ForcepointApprovedUSB`),"ApprovedUSB",
(`ForcepointKnownCDDVD`),"CDDVD",
(`ForcepointKnownMultiFunction`),"MultiFunction",
AlertType="Endpoint Applications" AND Device="Bluetooth","Bluetooth",
AlertType="Endpoint Removable Media" AND Device="Windows Portable Device (WPD)","WPD",
AlertType="Endpoint Removable Media" AND
Device!="Windows Portable Device (WPD)" AND NOT
(`ForcepointApprovedUSB`) AND NOT
(`ForcepointKnownCDDVD`) AND NOT
(`ForcepointKnownMultiFunction`),"UnApprovedUSB")
| join type=inner Username
[
search
index=restricted_emn_security
sourcetype=dlp_lt
| rename UserID as Username
| eval Check = "Yes"
| dedup Username
| fields Username, Check
]
| where isnotnull(EnumDeviceType) AND Check="Yes"
| eval Time=strftime(_time, "%B %d, %Y %H:%M %Z")
| table Time Username Name Action Source Filename Device Serial EnumDeviceType
| sort -Time</query>
<earliest>$TimeFrame.earliest$</earliest>
<latest>$TimeFrame.latest$</latest>
</search>
<option name="count">30</option>
<option name="drilldown">none</option>
</table>
</panel>
</row>
</form>
Шаблон, который я хотел бы заменить, это
index=restricted_security sourcetype=forcepoint
на
index=newname
sourcetype=forcepoint
Таким образом, любой шаблон, где
index=restricted_security
sourcetype=forcepoint
должен быть заменен на новое значение.
XML-файлы имеют много комбинаций, таких как
index=restricted_security
sourcetype=someother value, index=someindex sourcetype=forcepoint
и т. д., но их не нужно заменять.
Я пробовал много шаблонов, подобных ниже, с множеством комбинаций sed, но это, кажется, не работает
sed 's/index=restricted_security\s\nsourcetype=forcepoint/index=restricted_security sourcetype=forcepoint/g'
Может кто-то подсказать, как это заменить?
Используя Python’s lxml
:
import re
from lxml import etree
file_path="/tmp/file.xml"
tree = etree.parse(file_path)
root = tree.getroot()
xpath_expression = '//table/search/query/text()'
text_nodes = root.xpath(xpath_expression)
if text_nodes:
first_text_node = text_nodes[0]
lines = first_text_node.splitlines()
if lines and 'index=restricted_security' in lines[0]:
lines[0] = 'index=NEW_NAME'
updated_text="\n".join(lines)
parent_element = first_text_node.getparent()
parent_element.text = updated_text
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding='UTF-8')
Скрипт редактирует файл на месте.
Используя xmlstarlet
в качестве команды оболочки, в 2 вызова:
xmlstarlet ed -u '//table/search/query' -v "index=NEW_NAME
$(xmlstarlet sel -t -v '//table/search/query/text()' file.xml |
awk 'NR>1')" file.xml
Вы можете добавить переключатель -L
к xmlstarlet ed
, если вам нужно редактировать на месте.
Ответ или решение
Для замены строки в многострочных XML-файлах с использованием командной строки и утилиты sed
действительно могут возникнуть трудности, так как стандартный sed
не поддерживает работу с многострочными выражениями напрямую. Однако, ваши требования можно эффективно решить с помощью различных инструментов, например, с использованием Python и библиотеки lxml
, или с помощью утилиты xmlstarlet
.
1. Использование Python с lxml
Если вы хотите использовать Python для редактирования файла, вы можете сделать это следующим образом:
import re
from lxml import etree
file_path = "/tmp/file.xml"
# Парасим XML-файл
tree = etree.parse(file_path)
root = tree.getroot()
# XPath для нахождения нужного текста
xpath_expression = '//table/search/query/text()'
text_nodes = root.xpath(xpath_expression)
# Проверка наличия нужного текста и его замена
if text_nodes:
for text_node in text_nodes:
if 'index=restricted_security' in text_node:
updated_text = text_node.replace('index=restricted_security', 'index=newname')
parent_element = text_node.getparent()
parent_element.text = updated_text
# Запись изменений обратно в файл
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding='UTF-8')
Этот скрипт читает XML-файл, находит нужный текст и заменяет его. После этого он сохраняет изменения обратно в файл.
2. Использование xmlstarlet
Если вам более удобно работать в командной строке, вы можете использовать xmlstarlet
, который позволяет модифицировать XML-структуры:
xmlstarlet ed -u '//table/search/query' -v "index=newname\n$(xmlstarlet sel -t -v '//table/search/query/text()' file.xml | awk 'NR>1')" file.xml
Это командное выражение заменяет нужное значение непосредственно в XML-файле. Чтобы изменить файл на месте, вы можете добавить ключ -L
:
xmlstarlet ed -L -u '//table/search/query' -v "index=newname\n$(xmlstarlet sel -t -v '//table/search/query/text()' file.xml | awk 'NR>1')" file.xml
Заключение
В обоих случаях мы решаем проблему выполнения замены в многострочных XML-файлах. Выбор между Python и xmlstarlet
зависит от ваших предпочтений и рабочего окружения. Python дает вам больше гибкости и возможностей для сложных операций с структурой документа, в то время как xmlstarlet
может быть более удобным для простых задач из командной строки.