Как создать книжный индекс с помощью sed

В письме 1997 года, позднее исправленном по мелочам в 2001-м, Эрик Пемент рассказывает, как после вёрстки книги готовил предметный указатель. На бумажных корректурах отмечали нужные термины, затем добровольцы заносили каждый термин и номер страницы в отдельную строку через точку с запятой, например, «атеизм; 1». Записи сначала сортировались без учёта регистра по термину и численно по номеру страницы: это нужно, чтобы страница 3 шла раньше 19.

Далее sed-скрипт объединял соседние строки с одним и тем же термином: вместо отдельных «Adam; 13», «Adam; 21» и «Adam; 30-32» получалась строка «Adam, 13, 21, 30-32». Для этого скрипт добавлял следующую строку в буфер, сравнивал её начало с предыдущим термином, при совпадении заменял перевод строки запятой и продолжал цикл. Когда термин менялся, он заменял точку с запятой на запятую, печатал первую строку буфера и переходил к следующей.

Пемент обнаружил уязвимость: если хотя бы в одной записи нет точки с запятой, такая строка в итоге оказывается первой в буфере. Команда замены тогда удаляет точку с запятой уже во второй, исправной строке; после этого скрипт перестаёт корректно объединять все следующие записи. Автор предлагает два исправления. Первое, перед основной обработкой проверять каждую строку: при отсутствии точки с запятой вывести сообщение и номер строки, затем завершить работу; также можно отдельно отклонять строки с двумя точками с запятой. Второе, ограничить замену точки с запятой первой строкой буфера: обработка продолжится, но неверная строка будет напечатана отдельно и не войдёт в объединённый список. Автор предпочитает первый вариант.

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

Ключевые факты

  • Исходные записи имеют вид «термин; номер страницы» и должны быть отсортированы без учёта регистра по термину и численно по страницам.
  • Скрипт sed объединяет последовательные записи одного термина в одну строку с перечнем страниц через запятую.
  • Одна строка без точки с запятой может привести к преждевременной замене разделителей в следующих строках и испортить оставшийся вывод.
  • Надёжный вариант проверяет отсутствие и, при необходимости, повтор точки с запятой, выводит номер ошибочной строки и завершает обработку.
  • Альтернатива не останавливает сценарий, но оставляет неправильную строку отдельной и не добавляет её страницы к указателю.

Почему это важно

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

Кому это важно

Приём адресован тем, кто поддерживает текстовые конвейеры в Unix-подобной среде, обрабатывает отсортированные списки «ключ; значение» или хочет разобрать работу sed с несколькими строками в буфере.

Как это применить

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

Можно ли доверять

Это техническое письмо Эрика Пемента с полным примером входа, ожидаемого вывода и исходным кодом скрипта. Оно не является современным руководством по GNU sort: приведённый синтаксис sort относится к описанному в тексте времени и может отличаться в нынешних системах.

Риски и подводные камни

Скрипт рассчитывает на предварительную сортировку и строго заданный разделитель. Вариант, который продолжает работу после ошибки, сохраняет неверную строку в выводе, но не включает её в список страниц; вариант с остановкой требует исправить исходные данные и запустить обработку заново.

«Лично я выбрал бы первое решение, остановить обработку файла при обнаружении некорректных входных данных.»

— Эрик Пемент