Вопрос или проблема
Я тестирую это на довольно старой системе Debian 10, используя
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)
в
GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)
Обе команды awk
и gawk
вызывают эту же версию GNU Awk.
Используя эти ответы[1][2][3], я пытаюсь написать скрипт, который обнаруживает конфликты объединения gettext, издаваемые msgcat.
Тестовый входной текст в формате “plaintext”, который далее называется merged_file.po
:
"#-#-#-#-# de.po (Application Library) #-#-#-#-#\n"
"#-#-#-#-# de.po (Middleware Library) #-#-#-#-#\n"
"#-#-#-#-# de.po #-#-#-#-#\n"
Я выбрал awk вместо grep и sed, чтобы пропустить заголовок, используя NR > <line number>
. Поскольку это работает без проблем, я опускаю это здесь для краткости.
Синтаксис строки:
"#-#-#-#-#
- имя исходного файла
(
Project-Id-Version)
, если задано в исходном файле#-#-#-#-#\n"
Регулярное выражение, построенное с использованием RegExr и проверенное во всех вариантах, поддерживаемых regex101: #-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?#-#-#-#-#
(Обратите внимание, что это предполагает, что имя файла не содержит пробелов – на данный момент меня это не волнует.)
Предполагаемый эффект двоякий:
- найти все вхождения в выходном .po файле, чтобы выдать сообщение об ошибке
- захватить имя библиотеки в группе захвата 1, чтобы сделать сообщение об ошибке более читаемым (особенно для людей, не слишком знакомых с gettext)
Вот вызовы, которые я пробовал:
- Рабочая базовая линия
awk '/#-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?/ { print NR, $0 }' merged_file.po
находит все вхождения и печатает всю строку. awk '/#-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?#/ { print NR, $0 }' merged_file.po
отбрасывает все вхождения с Project-Id-Versionawk 'match($0, /#-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?/, library_name) { print NR, "from library \047"library_name[1]"\047" }' merged_file.po
печатает пустую строку вместо<Project-Id-Version>
library_name[0]
содержит строку до неконцептуальной группы, так что, по-видимому,match
вообще не издаёт группы захвата – если бы это было так,library_name[0]
содержал бы всю строку.
awk '/#-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?/ { library_name = gensub(/#-#-#-#-#\s+\S+\s+(?:\(([^()]+)\)\s+)?/, "\\1", "g"); print NR, "from library \047"library_name"\047" }' merged_file.po
печатает"(<Project-Id-Version>) #-#-#-#-#\n"
вместо<Project-Id-Version>
\\0
действительно содержит всю строку.\\2
содержит ту же строку, что и\\1
. (ожидаемое: пусто)
Поддержка регулярных выражений в связанных инструментах, таких как grep или sed, часто на удивление ограничена, поэтому, вместо того, чтобы просто спрашивать, почему мои конкретные вызовы не работают, я бы хотел спросить более широко:
Чем отличается сопоставление регулярных выражений в GNU Awk от “нормы”?
(Определенно) валидные X/Y ответы:
- Я использую слишком старую версию. (Если да, то какую минимальную версию мне нужно иметь?)
- Я пропустил и мое регулярное выражение ошибочно. (Если да, то как?)
- Виноват Bash, и мне нужны некие загадочные экранирования. (Если да, то какие и почему?)
- Baeldung ошибается и все-таки существует гораздо более простое решение, не использующее Awk. (Если да, то какое?)
- Чтобы в будущем не столкнуться с той же проблемой, я хотел бы получить этот ответ в дополнение к, а не вместо ответа на сам вопрос. Я действительно хотел бы лучше понимать, что можно ожидать от Awk и что лучше не надеяться.)
Ответ или решение
GNU Awk является мощным инструментом для обработки текстовых данных, и понимание того, как он работает с регулярными выражениями и захватывающими группами, может быть критически важным для эффективного использования этой утилиты.
В версиях GNU Awk, начиная с 3.0, появилась поддержка функции match()
, которая позволяет работать с захватывающими группами. Эта функция принимает строку и регулярное выражение, и, если совпадение найдено, возвращает позицию начала совпадения. Кроме того, match()
может заполнять массив с информацией о совпадении, где первый элемент массива содержит всю строку, соответствующую регулярному выражению, а последующие элементы – содержимое захватывающих групп.
Теперь рассмотрим, почему в вашем случае функция match()
и gensub()
не дают ожидаемых результатов:
-
Регулярные выражения и
match()
:- В GNU Awk 4.2.1, функция
match()
не поддерживает именованные захватывающие группы. Она возвращает только содержимое неопциональных захватывающих групп. - Проблема в вашем случае может заключаться в том, что вы ожидаете, что
match()
будет работать по аналогии с Perl или другими языками, которые обладают более продвинутыми возможностями обработки регулярных выражений. Поскольку в вашем регулярном выражении захватывающая группа находится в опциональном блоке(?: ... )?
, вы видите пустую строку, когда группа не захватывает ничего.
- В GNU Awk 4.2.1, функция
-
Использование
gensub()
:- Функция
gensub()
в GNU Awk позволяет заменять текст по регулярному выражению и поддерживает захватывающие группы. Проблема заключается в том, что вы используете "\1" для извлечения первой захватывающей группы, но она может быть пустой из-за того, что группа является необязательной в регулярном выражении. Это приводит к тому, что вместо ожидаемогоProject-Id-Version
возвращается часть строки после захватывающей группы.
- Функция
-
Решение:
- Измените свое регулярное выражение так, чтобы захватывающая группа всегда выполнялась. Можно рассмотреть вариант использования отдельного пошагового разбора строки без использования опциональных захватывающих групп.
- Также вы можете предварительно фильтровать строки, не содержащие
Project-Id-Version
, и применять регулярное выражение только к оставшимся строкам.
-
Альтернативные решения:
- В некоторых случаях может оказаться полезным написать скрипт на Python или Perl, где работа с регулярными выражениями и захватывающими группами более интуитивна и мощная, чем в Awk.
Таким образом, для обнаружения конфликтов в выходном файле .po вам, возможно, придётся скорректировать регулярные выражения или выбрать другой инструмент, более подходящий для сложных текстовых преобразований. Понимание особенностей разных инструментов, таких как Awk, может значительно повысить эффективность обработки данных и помогает избежать распространённых ошибок.