Необходимо сделать несколько замен строк в файле.

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

У меня есть файл с несколькими “абзацами”, подобными этому:

<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>

Мне нужно заменить КАЖДОЕ вхождение этого абзаца на:

<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/File Name</filename>
<transname/>

Я пытался МНОГО раз заменить каждое вхождение:

  • разбивая строфы с помощью awk (работает)
  • сохраняя оригинал как $ORIGTEXT (работает)
  • заменяя строки filename/transname (работает)
  • сохраняя НОВЫЙ многострочный текст как $NEWTEXT (работает, замена одиночных строк)
  • sed оригинальный файл, чтобы заменить каждый абзац на новый (НЕ работает)

выполняю sed -i "s|$ORIGTEXT|$NEWTEXT|g", но это не работает. Я предполагаю, что дело в специальных символах в файле вместе с символами новой строки в переменных NEW/ORIG… может кто-то помочь??

После добавления обертывающего тега r к вашему XML:

xmlstarlet ed -u '//filename' -v 'FILE_PATH/File Name' \
    -u '//transname' -v '' file.xml
<?xml version="1.0"?>
<r>
  <type>TRANS</type>
  <attributes/>
  <specification_method>rep_name</specification_method>
  <trans_object_id/>
  <filename>FILE_PATH/File Name</filename>
  <transname/>
</r>

Предполагая, что вы хотите изменить только блоки текста, которые точно совпадают с блоком, показанным в вашем вопросе, и что ваш ввод всегда выглядит точно так, как показано в вашем вопросе, то с помощью любого POSIX awk вы можете сделать:

$ cat tst.sh
#!/usr/bin/env bash

IFS= read -r -d '' old <<-'    !'
        <type>TRANS</type>
        <attributes/>
        <specification_method>rep_name</specification_method>
        <trans_object_id/>
        <filename/>
        <transname>\File Name</transname>
    !

IFS= read -r -d '' new <<-'    !'
        <type>TRANS</type>
        <attributes/>
        <specification_method>rep_name</specification_method>
        <trans_object_id/>
        <filename>FILE_PATH/File Name</filename>
        <transname/>
    !

old="$old" new="$new" \
awk '
    { rec = (NR>1 ? rec ORS : "") $0 }
    END {
        old = ENVIRON["old"]
        new = ENVIRON["new"]

        # Экранируем любые возможные метасимволы regexp в "old" и
        # любые возможные метассылки в "new":
        gsub( /[^^\\]/ ,  "[&]", old)
        gsub(     /\^/ ,  "\\^", old)
        gsub(     /\\/ , "\\\\", old)
        gsub(      /&/ ,  "\\&", new)

        # Заменяем каждое "old" на "new":
        gsub( old, new, rec )

        print rec
    }
' "${@:--}"

Например, учитывая следующий ввод, где 3-й блок является целевым для OP из вопроса, и есть другие похожие блоки, которые не должны изменяться, так как они не совсем совпадают с целевым блоком OP:

$ cat file
<type>CONST</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>Foo Bar</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
<type>TRANS</type>
<attributes/>
<specification_method>other rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>final<filename/>
<transname>\File Name</transname>

$ ./tst.sh file
<type>CONST</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>Foo Bar</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/File Name</filename>
<transname/>
<type>TRANS</type>
<attributes/>
<specification_method>other rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>final<filename/>
<transname>\File Name</transname>

Обратите внимание, что ТОЛЬКО 3-й блок, целевой, изменился, и только нужные строки внутри этого блока изменились. Это легче увидеть, используя diff между входным файлом и выводом команды:

$ diff file <(./tst.sh file)
17,18c17,18
< <filename/>
< <transname>\File Name</transname>
---
> <filename>FILE_PATH/File Name</filename>
> <transname/>

$ diff -y file <(./tst.sh file)
<type>CONST</type>                                              <type>CONST</type>
<attributes/>                                                   <attributes/>
<specification_method>rep_name</specification_method>           <specification_method>rep_name</specification_method>
<trans_object_id/>                                              <trans_object_id/>
<filename/>                                                     <filename/>
<transname>\File Name</transname>                               <transname>\File Name</transname>
<type>TRANS</type>                                              <type>TRANS</type>
<attributes/>                                                   <attributes/>
<specification_method>rep_name</specification_method>           <specification_method>rep_name</specification_method>
<trans_object_id/>                                              <trans_object_id/>
<filename/>                                                     <filename/>
<transname>Foo Bar</transname>                                  <transname>Foo Bar</transname>
<type>TRANS</type>                                              <type>TRANS</type>
<attributes/>                                                   <attributes/>
<specification_method>rep_name</specification_method>           <specification_method>rep_name</specification_method>
<trans_object_id/>                                              <trans_object_id/>
<filename>                                                   | <filename>FILE_PATH/File Name</filename>
<transname>\File Name</transname>                             | <transname/>
<type>TRANS</type>                                              <type>TRANS</type>
<attributes/>                                                   <attributes/>
<specification_method>other rep_name</specification_method>     <specification_method>other rep_name</specification_method>
<trans_object_id/>                                              <trans_object_id/>
<filename/>                                                     <filename/>
<transname>\File Name</transname>                               <transname>\File Name</transname>
<type>TRANS</type>                                              <type>TRANS</type>
<attributes/>                                                   <attributes/>
<specification_method>rep_name</specification_method>           <specification_method>rep_name</specification_method>
<trans_object_id/>                                              <trans_object_id/>
<filename>final<filename/>                                      <filename>final<filename/>
<transname>\File Name</transname>                               <transname>\File Name</transname>

Что касается here-documents, пробел перед ! в разделителях равен 4 пробелам, а пробел в начале каждой строки внутри каждого here-doc — это табуляция.

Смотрите:

С GNU awk вы можете заменить содержимое входного файла, используя awk -i inplace '...', или, как и с любым инструментом, вы можете просто создать/использовать свой собственный временный файл:

tmp=$(mktemp) &&
trap 'rm -f "$tmp"; exit' EXIT &&
awk '...' "$1" > "$tmp" &&
mv -- "$tmp" "$1"

Используя язык TXR, у нас есть следующая программа в файле data.txr:

@(repeat)
@  (cases)
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\@fname</transname>
@    (output)
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/@fname</filename>
<transname/>
@    (end)
@  (or)
@line
@    (do (put-line line))
@  (end)
@(end)

применяется к следующему файлу data:

$ cat data
AAAA
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name AAA</transname>
BBBB
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name BBB</transname>
CCCC

Запускается:

$ txr data.txr data
AAAA
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/File Name AAA</filename>
<transname/>
BBBB
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/File Name BBB</filename>
<transname/>
CCCC

Отредактировано в Vim с использованием файла синтаксиса txr.vim, выглядит следующим образом:

Синтаксически окрашенный код TXR

Ответ или решение

Для решения задачи замены нескольких многострочных фрагментов текста в файле, представленного в формате, аналогичном XML, можно использовать различные инструменты, включая awk и sed. Однако, как вы уже заметили, использование sed для работы с многострочными фрагментами может быть затруднено из-за необходимости обработки специальных символов и символов новой строки. В данной статье мы подробно рассмотрим, как решить эту задачу, используя awk, и представим пример кода.

Шаги по решению задачи

  1. Определение оригинального и нового текста: Ваша задача заключается в замене блока текста, содержащего <filename> и <transname>, на новый блок с заданными значениями.

  2. Использование awk для многострочной замены:

    • Мы используем переменные окружения для хранения старого и нового текста.
    • Затем применяем awk для фильтрации и замены только тех блоков, которые соответствуют введенному шаблону.

Пример кода

#!/usr/bin/env bash

# Определяем оригинальный текст для замены
IFS= read -r -d '' old <<-'!'
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename/>
<transname>\File Name</transname>
!

# Новый текст, который мы хотим вставить
IFS= read -r -d '' new <<-'!'
<type>TRANS</type>
<attributes/>
<specification_method>rep_name</specification_method>
<trans_object_id/>
<filename>FILE_PATH/File Name</filename>
<transname/>
!

# Запуск операции в awk
old="$old" new="$new" \
awk '
    { rec = (NR > 1 ? rec ORS : "") $0 }
    END {
        # Извлекаем значения из переменных окружения
        old = ENVIRON["old"]
        new = ENVIRON["new"]

        # Экранируем специальные символы
        gsub(/[^\\]/, "[&]", old)
        gsub(/\^/, "\\^", old)
        gsub(/\\/, "\\\\", old)
        gsub(/&/, "\\&", new)

        # Выполняем замену
        gsub(old, new, rec)

        print rec
    }
' "$@"

Объяснение кода

  • Определение текста: Мы используем here-doc для создания многострочного текста, который соответствует оригинальному состоянию и нужному результату.
  • Использование awk: Скрипт объединяет файл в один текст с помощью переменной rec, а затем производит замену при помощи gsub().
  • Экранирование символов: Это необходимо для предотвращения некорректной интерпретации специальных символов в регулярных выражениях.

Применение

Данный скрипт можно использовать для обработки любого файла, содержащего блоки текста в указанном формате. Просто передайте имя файла в командной строке.

Заключение

Использование awk для задач, связанных с многострочным текстом, обеспечивает высокую точность замены и гибкость в обработке данных. Данный метод позволяет избежать многих проблем, связанных с использованием sed, особенно при работе с многострочными фрагментами и специальными символами. بخотите узнать больше или нужны дополнительные примеры на основе вашего специфического контекста? Обязательно дайте знать!

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

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