Команда sed не сопоставляет и не заменяет шаблон в многострочном XML

Вопрос или проблема

У меня есть много 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=(?&lt;Action&gt;.*?)\s.*)"
| rex field=_raw "(.*duser=(?&lt;Device&gt;.*?)(:\s\d|;|\sfname=).*)"
| rex field=_raw "(.*duser=.*?;\s(?&lt;Serial&gt;.*?)\sfname=)"
| rex field=_raw "(.*fname=(?&lt;Filename&gt;.*?)\smsg=.*)"
| rex field=_raw "(.*fname=.:\\\(?&lt;RawFilename&gt;.*)(?:\s-\s.*)\smsg=.*)"
| rex field=_raw "(.*suser=(?&lt;Name&gt;.*)\scat=.*)"
| rex field=_raw "(.*loginName=.*\\\\(?&lt;Username&gt;.*)\ssourceIp=.*)"
| rex field=_raw "(.*sourceIp=(?&lt;IP&gt;.*)\sseverityType=.*)"
| rex field=_raw "(.*sourceHost=(?&lt;Source&gt;.*)\sproductVersion=.*)"
| rex field=_raw "(.*sourceServiceName=(?&lt;AlertType&gt;.*)\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=(?&lt;Action&gt;.*?)\s.*)"
| rex field=_raw "(.*duser=(?&lt;Device&gt;.*?)(:\s\d|;|\sfname=).*)"
| rex field=_raw "(.*duser=.*?;\s(?&lt;Serial&gt;.*?)\sfname=)"
| rex field=_raw "(.*fname=(?&lt;Filename&gt;.*?)\smsg=.*)"
| rex field=_raw "(.*fname=.:\\\(?&lt;RawFilename&gt;.*)(?:\s-\s.*)\smsg=.*)"
| rex field=_raw "(.*suser=(?&lt;Name&gt;.*)\scat=.*)"
| rex field=_raw "(.*loginName=.*\\\\(?&lt;Username&gt;.*)\ssourceIp=.*)"
| rex field=_raw "(.*sourceIp=(?&lt;IP&gt;.*)\sseverityType=.*)"
| rex field=_raw "(.*sourceHost=(?&lt;Source&gt;.*)\sproductVersion=.*)"
| rex field=_raw "(.*sourceServiceName=(?&lt;AlertType&gt;.*)\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 может быть более удобным для простых задач из командной строки.

Оцените материал
Добавить комментарий

Капча загружается...