Замените текст в скобках.

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

Я использую awk '{ gsub(/BAR|WIBBLE/, "FOO"); print }', чтобы заменить текст в данных, таких как:

ЧТО-ТО [BAR, WIBBLE]
ЧТО-ТО [BAR]

Это дает желаемый результат:

ЧТО-ТО [FOO, FOO]
ЧТО-ТО [FOO]

Но теперь мне пришлось обновить текст, который нужно заменить, на что-то вроде:

awk '{ gsub(/BAR|WIBBLE|ME/, "FOO"); print }'

Что преобразует текст, такой как:

ЧТО-ТО [ME, WIBBLE]

в:

ЧТО-ТО [FOO, FOO]

Как я могу ограничить свою замену только текстом между скобками (т.е. оставить ЧТО-ТО в покое)?

ИЗМЕНЕНИЕ

Мне также нужна надежность для любого текста, который может быть в ЧТО-ТО (например, ОНА ДАЛА МНЕ ЭТО не должна иметь ME заменен).

Должен ли это быть awk? Это намного проще на других языках, где часть замены может быть вызовом функции. Например, perl:

perl -pe 'sub c{$s=shift;$s=~s/\b(BAR|WIBBLE|ME)\b/FOO/g;$s}s/\[.*?\]/c$&/ge' 

Или короче, без использования подпрограммы:

perl -pe 's/\[.*?\]/$&=~s!\b(BAR|WIBBLE|ME)\b!FOO!gr/ge'

Попробуйте онлайн!

С GNU awk вы можете установить RS на содержимое скобок, а затем выполнить замену на RT (совпадающий разделитель записи):

awk -v RS='\\[[^]]*\\]' '{ gsub(/\<(BAR|WIBBLE|ME)\>/, "FOO", RT); printf "%s%s", $0, RT }' infile

infile:

cat << EOF > infile
ОНА ДАЛА МНЕ ЭТО
ЧТО-ТО [ME, WIBBLE, SOMMER]
EOF

вывод:

ОНА ДАЛА МНЕ ЭТО
ЧТО-ТО [FOO, FOO, SOMMER]

Awk не поддерживает обратные ссылки в заменах регулярных выражений, поэтому ему сложно выполнять замены в контексте. Sed может это сделать:

sed -e 's/\(\[[^]]*\)BAR/\1FOO/' 's/\(\[[^]]*\)ME/\1FOO/'

Если ваш sed поддерживает альтернативы в регулярных выражениях:

sed -e 's/\(\[[^]]*\)\(BAR\|ME\)/\1FOO/'

Это обрабатывает только одну замену внутри каждой пары скобок, даже с суффиксом g, потому что [^]]* совпадает с самой длинной последовательностью без закрывающей скобки. Для замены всех используйте явный цикл; обратите внимание, что это работает, только если FOO не является подстрокой BAR или ME.

sed -e ': a' -e 's/\(\[[^]]*\)BAR/\1FOO/' -e 't a' \
             -e 's/\(\[[^]]*\)ME/\1FOO/' -e 't a'

Если вам нужно что-то более сложное, используйте perl.

Используя Raku (ранее известный как Perl_6)

~$ raku -ne '.subst(:global:exhaustive, / "[" ~ "]" [ .*? <|w> <( [BAR|WIBBLE|ME] )> <|w> .*? ] /, "FOO").put;'  file

#ИЛИ:

~$ raku -ne 'BEGIN my @a = <BAR WIBBLE ME>; 
             .subst(:global:exhaustive, / "[" ~ "]" [ .*? <|w> <(@a)> <|w> .*? ] /, "FOO" ).put;'  file 

Приведенный выше ответ написан на Raku, который является членом семьи языков программирования Perl:

  1. Он использует конструкцию Raku ~ тильда для вложенных структур, чтобы нацелиться на правильные замены.
  2. Чтобы совпадать, но затем оставить это совпадение неизменным, используйте <( )> маркеры захвата Raku.
  3. Чтобы «насытить» замены, используйте параметр :exhaustive.
  4. Нулевое поле границы слова в Raku записывается как <|w> или <?wb>. В качестве альтернативы вы можете использовать << или « для левой границы слова и >> или » для правой границы слова.

Пример входных данных:

ОНА ДАЛА МНЕ ЭТО
ЧТО-ТО [ME, WIBBLE, SOMMER]
ОНА ДАЛА МНЕ ЭТО [ОНА ДАЛА МНЕ ЭТО]

Пример выхода:

ОНА ДАЛА МНЕ ЭТО
ЧТО-ТО [FOO, FOO, SOMMER]
ОНА ДАЛА МНЕ ЭТО [ОНА ДАЛА FOO ЭТО]

https://docs.raku.org/language/regexes#Tilde_for_nesting_structures
https://docs.raku.org/language/regexes#Capture_markers:_%3C(_)%3E
https://raku.org

awk '{ gsub(/\bBAR\b|\bWIBBLE\b|\bME\b/, "FOO"); print }'

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

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

Проблема

Вы имеете текст, в котором необходимо заменить определенные слова (например, BAR, WIBBLE, ME) на другое слово (в данном случае FOO), но только внутри квадратных скобок. Если не ограничить область замены, вы рискуете затронуть текст вне скобок, что может повредить целостности данных.

Решение с использованием awk

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

awk '{ gsub(/\[([^]]*)\]/, "[" gensub(/BAR|WIBBLE|ME/, "FOO", "g", "\\1") "]"); print }'

Как это работает:

  1. gsub с регулярным выражением \[([^]]*)\]: ищет текст в квадратных скобках и использует ([^]]*) для захвата текста внутри скобок.
  2. gensub: заменяет найденные слова внутри захваченной группы на FOO.
  3. Конечный результат выводится посредством print, который включает оригинальный текст за пределами скобок.

Этот подход подталкивает awk к обработке только текста внутри скобок, избегая нежелательных замен в остальной части строки.

Использование perl для более сложных случаев

Если ваша система позволяет использовать perl, подобные операции можно выполнять более эффективно из-за мощных возможностей регулярных выражений. Пример следующей команды решит вашу задачу:

perl -pe 's/\[([^\]]*?)\]/"[" . ($1 =~ s/\b(BAR|WIBBLE|ME)\b/FOO/gr) . "]/ge'

Объяснение:

  • *s/[([^]]?)]/**: находит текст в квадратных скобках.
  • $1 =~ s/\b(BAR|WIBBLE|ME)\b/FOO/gr: внутри захваченной группы заменяет BAR, WIBBLE и ME на FOO, используя /g (глобально) и r для получения результата замены.

Так, конечный вывод будет правильно отражать текст с заменами только в указанных границах.

Итог

Выбор между awk и perl зависит от ваших предпочтений и требований к инструментам, которые у вас под рукой.

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

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

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