Как можно сделать так, чтобы 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 не совпадает в файле, вторая команда подстановки никогда не выполняется.
Учитывая эти ограничения:
- Флаг ‘e’ Vim является расширением и не требуется POSIX
- Реализации, предоставляющие несколько опций -c, нельзя полагаться на них
- sed -i никогда не следует использовать для редактирования файлов
Существует ли переносимый способ сделать так, чтобы ex игнорировал непарные шаблоны в командах подстановки?
Как заставить POSIX ex игнорировать несоответствующие шаблоны в командах замены
POSIX ex можно заставить игнорировать несоответствующие шаблоны в командах замены с помощью команды :global
вместе с командой замены, обеспечивая, что каждая замена выполняется независимо от того, соответствовали ли предыдущие шаблоны. Этот подход использует встроенные возможности POSIX без зависимости от расширений, специфичных для реализации, или обходных решений, нарушающих заданные ограничения.
Содержание
- Поведение POSIX ex с несоответствующими шаблонами
- Использование подхода с командой global
- Альтернативы с использованием скриптинга оболочки
- Практические примеры реализации
- Ограничения и соображения
- Заключение
Поведение 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
) самостоятельно, которая действительно прерывает последовательность, когда шаблон не находит соответствий.
# Это завершится с ошибкой, если pattern1 не существует:
ex -c 's/pattern1/replacement1/s/pattern2/replacement2/x' file
# Это работает независимо от того, соответствуют ли шаблоны:
ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/x' file
Альтернативы с использованием скриптинга оболочки
Для более сложных сценариев, скриптинг оболочки может обеспечить дополнительную гибкость, сохраняя соответствие POSIX. Эти подходы особенно полезны при обработке нескольких файлов или когда шаблоны и замены должны генерироваться динамически.
Подход с простым циклом
#!/bin/sh
file=$1
shift
while [ $# -ge 2 ]; do
pattern=$1
replacement=$2
shift 2
ex -c "g/$pattern/s//$replacement/|x" "$file"
done
Этот скрипт принимает файл, за которым следуют чередующиеся шаблоны и замены, применяя каждую замену отдельно.
Обработка файла с шаблонами
Для сценариев с множеством шаблонов замены:
#!/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). Вот как интегрировать подход, игнорирующий несоответствующие шаблоны:
find . -name "*.txt" -exec ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' {} +
Это эффективно обрабатывает несколько файлов, обеспечивая, что несоответствующие шаблоны не прерывают последовательность команд.
Практические примеры реализации
Множественные замены в одном файле
Для обработки одного файла с несколькими заменами:
ex -c 'g/OLD1/s//NEW1/|g/OLD2/s//NEW2/|g/OLD3/s//NEW3/|x' document.txt
Это попытается выполнить все три замены независимо от того, соответствовали ли предыдущие шаблоны.
Условные замены с шаблонами
Когда необходимо выполнять разные замены на основе разных шаблонов:
#!/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"
Ведение журнала для несоответствующих шаблонов
Для отладки или ведения журнала можно расширить подход скриптинга оболочки для отслеживания шаблонов, которые не соответствовали:
#!/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, есть некоторые ограничения, которые следует учитывать:
Соображения о производительности
Обработка каждой замены отдельно может быть менее эффективной, чем единый конвейер команд, особенно для больших файлов. Влияние на производительность обычно становится заметным только при:
- Очень больших файлах (много МБ или ГБ)
- Большом количестве шаблонов замены (десятки или более)
- Частом выполнении в плотных циклах
Для большинства распространенных случаев использования разница в производительности незначительна по сравнению с надежностью, получаемой.
Экранирование шаблонов
Когда шаблоны содержат специальные символы регулярных выражений или метасимволы оболочки, им необходимо правильное экранирование:
# Для сложных шаблонов используйте printf для обработки экранирования
pattern=$(printf "%s" "special.*chars+" | sed 's/[[\.*^$()+?{|]/\\&/g')
replacement=$(printf "%s" "replacement" | sed 's/[\/&]/\\&/g')
ex -c "g/$pattern/s//$replacement/|x" file
Вопросы атомарности
Каждая замена - это отдельная операция с файлом, что означает:
- Существуют промежуточные состояния между операциями
- Если скрипт прерывается в процессе выполнения, некоторые замены могут быть применены, в то время как другие - нет
- Файл изменяется между каждой заменой
Для критических операций рассмотрите возможность создания резервной копии сначала:
cp file.txt file.txt.backup
ex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' file.txt
Тестирование переносимости
Разные реализации Unix могут иметь тонкие различия в поведении ex. При написании скриптов для распространения:
- Тестируйте на нескольких платформах (Linux, BSD, Solaris и т.д.)
- Используйте наиболее базовые возможности POSIX
- Избегайте оптимизаций, специфичных для реализации
Заключение
Чтобы заставить POSIX ex игнорировать несоответствующие шаблоны в командах замены:
-
Используйте подход с командой
:global
- Это наиболее надежный метод, соответствующий POSIX, как вex -c 'g/pattern1/s//replacement1/|g/pattern2/s//replacement2/|x' file
. Каждая замена выполняется независимо от того, соответствовали ли предыдущие шаблоны. -
Используйте скриптинг оболочки для сложных сценариев - При работе с несколькими файлами или динамическими шаблонами, скрипты, обрабатывающие каждую замену отдельно, обеспечивают дополнительную гибкость, сохраняя соответствие POSIX.
-
Учитывайте компромиссы в производительности и атомарности - Хотя подход с командой global надежен, будьте внимательны к последствиям для производительности и атомарности при обработке больших файлов или множества шаблонов.
-
Тестируйте на разных реализациях - Поведение POSIX ex в целом согласовано, но тестирование на нескольких Unix-платформах обеспечивает максимальную переносимость для критических скриптов.
Этот подход удовлетворяет всем указанным ограничениям: он не полагается на флаг ‘e’ Vim, не зависит от нескольких опций -c и избегает sed -i, обеспечивая переносимое решение проблемы несоответствующих шаблонов в командах замены ex.