Вопрос или проблема
У меня есть файл с несколькими “абзацами”, подобными этому:
<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 — это табуляция.
Смотрите:
- is-it-possible-to-escape-regex-metacharacters-reliably-with-sed для объяснения
gsub()s
(эта статья касается sed, но также применима к метасимволам awk). - how-do-i-use-shell-variables-in-an-awk-script для того, как я использую переменные shell->awk.
С 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
, выглядит следующим образом:
Ответ или решение
Для решения задачи замены нескольких многострочных фрагментов текста в файле, представленного в формате, аналогичном XML, можно использовать различные инструменты, включая awk
и sed
. Однако, как вы уже заметили, использование sed
для работы с многострочными фрагментами может быть затруднено из-за необходимости обработки специальных символов и символов новой строки. В данной статье мы подробно рассмотрим, как решить эту задачу, используя awk
, и представим пример кода.
Шаги по решению задачи
-
Определение оригинального и нового текста: Ваша задача заключается в замене блока текста, содержащего
<filename>
и<transname>
, на новый блок с заданными значениями. -
Использование
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
, особенно при работе с многострочными фрагментами и специальными символами. بخотите узнать больше или нужны дополнительные примеры на основе вашего специфического контекста? Обязательно дайте знать!