GLM 4.5 Air

Как заставить POSIX ex игнорировать несоответствующие шаблоны

Узнайте, как заставить POSIX ex игнорировать несоответствующие шаблоны в командах замены. Узнайте о подходе с использованием глобальной команды, который работает на всех Unix-системах без использования нестандартных расширений.

Вопрос

Как можно сделать так, чтобы POSIX ex игнорировал непарные шаблоны в командах подстановки переносимо, при следующих ограничениях?

POSIX.1-2017 определяет две утилиты редактирования файлов, ориентированные на строки: ed(1) и ex(1). ex более эффективен для пакетного редактирования в неинтерактивном режиме, потому что POSIX требует, чтобы он принимал один или несколько операндов файлов, в то время как ed требуется принимать только один. Это означает, что редактирование с помощью ex избегает вызова fork(2) при использовании в основном exec find(1).

Проблема в том, что при поиске BRE, если не находится совпадающая строка, это считается ошибкой. Хотя это явно указано в руководстве по ed, все реализации ex ведут себя одинаково. Например, в ex -sc s/pattern1/repl1|s/pattern2/repl2|x file, если pattern1 не совпадает в файле, вторая команда подстановки никогда не выполняется.

Учитывая эти ограничения:

  1. Флаг ‘e’ Vim является расширением и не требуется POSIX
  2. Реализации, предоставляющие несколько опций -c, нельзя полагаться на них
  3. sed -i никогда не следует использовать для редактирования файлов

Существует ли переносимый способ сделать так, чтобы ex игнорировал непарные шаблоны в командах подстановки?

GLM 4.5 Air

Как заставить POSIX ex игнорировать несоответствующие шаблоны в командах замены

POSIX ex можно заставить игнорировать несоответствующие шаблоны в командах замены с помощью команды :global вместе с командой замены, обеспечивая, что каждая замена выполняется независимо от того, соответствовали ли предыдущие шаблоны. Этот подход использует встроенные возможности POSIX без зависимости от расширений, специфичных для реализации, или обходных решений, нарушающих заданные ограничения.

Содержание


Поведение POSIX ex с несоответствующими шаблонами

POSIX.1-2017 определяет, что при поиске BRE (Basic Regular Expression), который не находит соответствующих строк, это считается ошибкой. Это поведение согласовано во всех реализациях ex, хотя оно явно задокументировано в руководстве по ed.

Основная проблема проявляется при использовании конвейеров команд, таких как ex -sc "s/pattern1/repl1|s/pattern2/repl2|x" file. В этом сценарии, если pattern1 не соответствует ни одной строке в файле, вся последовательность команд прерывается, предотвращая выполнение pattern2. Это ограничение создает значительные проблемы в сценариях пакетной обработки, когда некоторые шаблоны могут отсутствовать во всех обрабатываемых файлах.

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


Использование подхода с командой global

Наиболее переносимое решение в POSIX ex - это использование команды :global (:g) вместе с командой замены (:s). Этот подход использует тот факт, что :global не выдает ошибок, когда шаблоны не соответствуют, позволяя последующим командам выполняться нормально.

Базовый синтаксис

Основной шаблон:

ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' file

Ключевые особенности этого подхода:

  • g/pattern/ ищет все строки, соответствующие шаблону, не завершаясь с ошибкой, если соответствий не найдено
  • s//replacement/ использует пустой шаблон //, который повторяет шаблон из предыдущей команды :global
  • Каждая команда замены обрабатывается независимо

Почему это работает

Команда :global в POSIX ex предназначена для работы с нулевым количеством соответствий - она просто не выполняет связанную с ней команду, если ни одна строка не соответствует, но не прерывает последовательность команд. Это контрастирует с командой замены (:s) самостоятельно, которая действительно прерывает последовательность, когда шаблон не находит соответствий.

bash
# Это завершится с ошибкой, если pattern1 не существует:
ex -c 's/pattern1/replacement1/s/pattern2/replacement2/x' file

# Это работает независимо от того, соответствуют ли шаблоны:
ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/x' file

Альтернативы с использованием скриптинга оболочки

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

Подход с простым циклом

bash
#!/bin/sh

file=$1
shift

while [ $# -ge 2 ]; do
  pattern=$1
  replacement=$2
  shift 2
  
  ex -c "g/$pattern/s//$replacement/|x" "$file"
done

Этот скрипт принимает файл, за которым следуют чередующиеся шаблоны и замены, применяя каждую замену отдельно.

Обработка файла с шаблонами

Для сценариев с множеством шаблонов замены:

bash
#!/bin/sh

file=$1
patterns=$2

while IFS= read -r pattern && IFS= read -r replacement; do
  ex -c "g/$pattern/s//$replacement/|x" "$file"
done < "$patterns"

Это считывает пары шаблон/замена из файла, позволяя создавать более сложные и поддерживаемые правила замены.

Интеграция с find

В исходном вопросе упоминалось использование ex с первичным элементом exec find(1). Вот как интегрировать подход, игнорирующий несоответствующие шаблоны:

bash
find . -name "*.txt" -exec ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' {} +

Это эффективно обрабатывает несколько файлов, обеспечивая, что несоответствующие шаблоны не прерывают последовательность команд.


Практические примеры реализации

Множественные замены в одном файле

Для обработки одного файла с несколькими заменами:

bash
ex -c 'g/OLD1/s//NEW1/|g/OLD2/s//NEW2/|g/OLD3/s//NEW3/|x' document.txt

Это попытается выполнить все три замены независимо от того, соответствовали ли предыдущие шаблоны.

Условные замены с шаблонами

Когда необходимо выполнять разные замены на основе разных шаблонов:

bash
#!/bin/sh

file="$1"

# Первый проход - замена всех вхождений "foo" на "bar"
ex -c "g/foo/s//bar/|x" "$file"

# Второй проход - замена всех вхождений "baz" на "qux"
ex -c "g/baz/s//qux/|x" "$file"

# Третий проход - замена всех вхождений "quux" на "corge"
ex -c "g/quux/s//corge/|x" "$file"

Ведение журнала для несоответствующих шаблонов

Для отладки или ведения журнала можно расширить подход скриптинга оболочки для отслеживания шаблонов, которые не соответствовали:

bash
#!/bin/sh

file="$1"
shift
log_file="substitution_log_$(date +%Y%m%d_%H%M%S).log"

echo "Начало замен на $(date)" > "$log_file"

while [ $# -ge 2 ]; do
  pattern="$1"
  replacement="$2"
  shift 2
  
  # Подсчет соответствий перед заменой
  match_count=$(grep -c "$pattern" "$file" 2>/dev/null || echo 0)
  
  if [ "$match_count" -gt 0 ]; then
    ex -c "g/$pattern/s//$replacement/|x" "$file"
    echo "Применено: $pattern -> $replacement ($match_count вхождений)" >> "$log_file"
  else
    echo "Пропущено: $pattern -> $replacement (соответствий не найдено)" >> "$log_file"
  fi
done

echo "Завершение замен на $(date)" >> "$log_file"
echo "Журнал сохранен в: $log_file"

Ограничения и соображения

Хотя описанные выше подходы работают в рамках ограничений POSIX, есть некоторые ограничения, которые следует учитывать:

Соображения о производительности

Обработка каждой замены отдельно может быть менее эффективной, чем единый конвейер команд, особенно для больших файлов. Влияние на производительность обычно становится заметным только при:

  • Очень больших файлах (много МБ или ГБ)
  • Большом количестве шаблонов замены (десятки или более)
  • Частом выполнении в плотных циклах

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

Экранирование шаблонов

Когда шаблоны содержат специальные символы регулярных выражений или метасимволы оболочки, им необходимо правильное экранирование:

bash
# Для сложных шаблонов используйте printf для обработки экранирования
pattern=$(printf "%s" "special.*chars+" | sed 's/[[\.*^$()+?{|]/\\&/g')
replacement=$(printf "%s" "replacement" | sed 's/[\/&]/\\&/g')

ex -c "g/$pattern/s//$replacement/|x" file

Вопросы атомарности

Каждая замена - это отдельная операция с файлом, что означает:

  • Существуют промежуточные состояния между операциями
  • Если скрипт прерывается в процессе выполнения, некоторые замены могут быть применены, в то время как другие - нет
  • Файл изменяется между каждой заменой

Для критических операций рассмотрите возможность создания резервной копии сначала:

bash
cp file.txt file.txt.backup
ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' file.txt

Тестирование переносимости

Разные реализации Unix могут иметь тонкие различия в поведении ex. При написании скриптов для распространения:

  1. Тестируйте на нескольких платформах (Linux, BSD, Solaris и т.д.)
  2. Используйте наиболее базовые возможности POSIX
  3. Избегайте оптимизаций, специфичных для реализации

Заключение

Чтобы заставить POSIX ex игнорировать несоответствующие шаблоны в командах замены:

  1. Используйте подход с командой :global - Это наиболее надежный метод, соответствующий POSIX, как в ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' file. Каждая замена выполняется независимо от того, соответствовали ли предыдущие шаблоны.

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

  3. Учитывайте компромиссы в производительности и атомарности - Хотя подход с командой global надежен, будьте внимательны к последствиям для производительности и атомарности при обработке больших файлов или множества шаблонов.

  4. Тестируйте на разных реализациях - Поведение POSIX ex в целом согласовано, но тестирование на нескольких Unix-платформах обеспечивает максимальную переносимость для критических скриптов.

Этот подход удовлетворяет всем указанным ограничениям: он не полагается на флаг ‘e’ Vim, не зависит от нескольких опций -c и избегает sed -i, обеспечивая переносимое решение проблемы несоответствующих шаблонов в командах замены ex.