Вопрос или проблема
Я использую 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:
- Он использует конструкцию Raku
~
тильда для вложенных структур, чтобы нацелиться на правильные замены. - Чтобы совпадать, но затем оставить это совпадение неизменным, используйте
<( )>
маркеры захвата Raku. - Чтобы «насытить» замены, используйте параметр
:exhaustive
. - Нулевое поле границы слова в 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 }'
Как это работает:
- gsub с регулярным выражением
\[([^]]*)\]
: ищет текст в квадратных скобках и использует([^]]*)
для захвата текста внутри скобок. - gensub: заменяет найденные слова внутри захваченной группы на
FOO
. - Конечный результат выводится посредством
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
. В любом случае, эти методы гарантируют, что текст вне квадратных скобок останется нетронутым, обеспечивая целостность ваших данных.