sed -i правит не задумываясь, а поиск с заменой в редакторе не помнит, что сделал. sr 7.0.1 занимает нишу между ними: это замена по дереву файлов, где каждая правка записывается в сессию, которую можно посмотреть, сравнить и откатить одной командой. Ниже — что умеет утилита, что показал прогон на живых файлах, где проходит граница поддерживаемых платформ и какие грабли ждут на Windows.

sr search replace cover

TL;DR: что это и как проверить

  1. Что это. Один файл Python без сторонних зависимостей: сканирует дерево, находит, показывает диф, правит, ведёт журнал.
  2. Сопоставление литеральное по умолчанию. Регулярные выражения — по флагу, а не «как получится».
  3. Ничего не пишется до полного скана. Сначала подсчёт по всему дереву, потом запись.
  4. Журнал в .sr. Каждый прогон — сессия с идентификатором; откат по нему.
  5. Проверка. sr doctor — 13 проверок установки, и sr replace ... --dry-run.
sr find "old_api" -p '*.py'          # где встречается
sr diff "old_api" "new_api" -p '*.py'  # что изменится
sr replace "old_api" "new_api" -p '*.py'
sr rollback <id>                      # откат

Проблема, которую это решает

Массовая замена в большом проекте — операция, где легко потерять контроль. Классический сценарий: устаревший вызов API встречается в двухстах файлах, его надо переименовать. sed -i справится, но у него три беды.

Первая: нет предпросмотра. Вы не видите, что именно изменится, пока не изменилось. Опечатка в шаблоне стоит часа работы с git.

Вторая: нет памяти. Через неделю никто не скажет, какой прогон что правил: останется только история коммитов, где все двести файлов в одном.

Третья: нет точечного откатa. git checkout вернёт всё, включая правки, сделанные после.

Утилита закрывает все три: diff показывает план, sessions хранит журнал, rollback отменяет конкретный прогон. Плюс отдельная деталь, которая экономит нервы — сопоставление литеральное по умолчанию, то есть точка в строке поиска остаётся точкой, а не «любым символом».

Что такое sr-search-replace

flowchart TB
  FIND["find: где встречается"] --> DEC{"менять?"}
  DIFF["diff: что изменится"] --> DEC
  DEC -- да --> REP["replace: правка и сессия"]
  DEC -- нет --> STOP["стоп"]
  REP --> SESS["sessions: журнал прогонов"]
  SESS --> RB["rollback: откат"]
  DOC["doctor: проверка установки"] --> REP
Свойство Значение
Версия в разборе 7.0.1
Рантайм Python 3.10 или новее
Зависимости нет сторонних на рантайме
Форма поставки sr.pyz, wheel, sdist
Платформы Linux, macOS, WSL2
Журнал каталог .sr внутри целевого дерева
Лицензия (тег 7.0.1) MIT в LICENSE и pyproject.toml

Отдельно про поставку: в релизе лежат три артефакта — sr.pyz на 495 680 байт с файлом контрольной суммы, wheel на 184 817 байт и исходник на 242 704 байта. pyz удобен тем, что не требует установки пакетов: скачал, проверил сумму, запустил.

Возможности

Поиск и предпросмотр

Команда Что делает
find (он же search, grep) показывает совпадения: файл, строка, колонка
diff печатает унифицированный диф без изменения файлов
replace --dry-run считает и показывает сводку, ничего не записывая
--json, --porcelain машиночитаемый вывод для конвейеров

Правка

Команда Что делает
replace (r) заменяет в файлах, создаёт сессию
rename (mv) переименовывает файлы и каталоги по шаблону
-p, --include, --exclude, --exclude-dir отбор файлов
--max-size, --min-size, --newer-than, --older-than отбор по размеру и возрасту
--gitignore, --hidden, --symlinks поведение обхода

Журнал и откат

flowchart LR
  SCAN["скан всего дерева"] --> COUNT["подсчёт совпадений"]
  COUNT --> WRITE["запись изменений"]
  WRITE --> JOURNAL["сессия в .sr"]
  JOURNAL --> UNDO["откат по идентификатору"]
  UNDO --> CLEAN["очистка старых сессий"]
Команда Что делает
sessions список прогонов с числом файлов и замен
sessions show <id> подробности одной сессии
rollback откат последней или указанной сессии
sessions size сколько занимает журнал на диске
--keep, --keep-age сколько сессий хранить (по умолчанию 20)

Служебное

Команда Что делает
doctor проверка установки и возможностей файловой системы
update --check есть ли новая версия
completions генерация дополнений для оболочки
help options полный список флагов

Как это работает

Порядок подчинён одной идее: сначала узнать, потом менять.

Скан собирает совпадения по всему дереву и только после этого начинается запись. Это значит, что ошибка в отборе файлов обнаруживается до того, как что-то испорчено: сводка показывает число файлов и замен, и неверная цифра — повод остановиться.

Каждая изменяющая команда оставляет сессию: идентификатор вида 20261113T030219Z-5b3ed3, число файлов, число замен, текст шаблона. Сессии лежат в .sr внутри целевого дерева — то есть журнал путешествует вместе с проектом и попадает в .gitignore, а не в домашний каталог пользователя.

Откат восстанавливает исходное содержимое файлов сессии. Это не «отмена последнего действия в редакторе»: откатывается весь прогон целиком, что для массовой замены как раз и нужно.

Замеры и цифры

Проверено 13.11.2026, двумя проверками.

Что Значение Как
Архив тега v7.0.1 7 458 640 байт, SHA-256 4081f9a5…a692e84 скачивание тега и подсчёт хеша
Файлов в дереве 116, из них 18 файлов тестов разбор tar
Артефакты релиза sr.pyz 495 680 Б + .sha256, wheel 184 817 Б, sdist 242 704 Б просмотр релиза
find по трём файлам 3 совпадения в 3 файлах, скан 3 файлов, 8 мс прогон
diff унифицированный диф по трём файлам, «3 файла изменятся, 3 замены» прогон
replace --dry-run 3 файла, 3 замены, прочитано 57 байт, 3 мс, предупреждение «файлы не изменены» прогон
replace (реально) 3 файла, 3 замены, записано 57 байт, 24 мс, сессия 20261113T030219Z-5b3ed3 прогон
sessions одна сессия, 3 файла, 3 замены, шаблон 'old_api' → 'new_api' прогон
sessions size 2 записи, журнал 7,3 КБ прогон
doctor 13 проверок: 10 пройдено, 2 предупреждения, 0 отказов, 307 мс прогон
rollback без терминала отказ: «not a terminal, assuming ‘no’», файлы не тронуты прогон
rollback --yes на Windows восстановил первый файл и упал на os.chown прогон
PyPI, GitHub, вики проекта 200 / 200 / 200 curl
npm-страница пакета 403 — защита от автоматических запросов curl
sr 7.0.1: прогон на живых файлах и проверки установки

Главное из этих чисел — про откат. На Linux и macOS он штатный, а на Windows всё ломается: утилита восстанавливает первый файл и падает на os.chown, потому что в Windows такой функции нет. Дерево остаётся в смешанном состоянии: часть файлов откачена, часть — нет. Это не случайность, а следствие платформенной границы, и ниже она описана отдельно.

Сильные стороны продукта

  • Журнал вместо памяти. Каждый прогон записан, и через месяц видно, что и когда менялось.
  • Точечный откат. Отменяется конкретная сессия, а не всё подряд через git.
  • Литеральное сопоставление по умолчанию. Регулярки включаются осознанно, а не по умолчанию.
  • Предпросмотр на трёх уровнях. find — где, diff — что, --dry-run — сколько.
  • Ноль зависимостей. Python 3.10 и всё; sr.pyz запускается без установки пакетов.
  • Встроенная самопроверка. doctor проверяет не только версию, но и возможности файловой системы.
  • Машиночитаемый вывод. --json и --porcelain для конвейеров.
  • Защита от опасных прогонов. Отказ работать от root и в системных каталогах без явного флага.

Установка

# из релиза: pyz с контрольной суммой
curl -LO https://github.com/paulmann/sr-search-replace/releases/download/v7.0.1/sr.pyz
sha256sum -c sr.pyz.sha256
python3 sr.pyz doctor

# из PyPI
pipx install sr-search-replace

# из клона
git clone https://github.com/paulmann/sr-search-replace.git
cd sr-search-replace && python3 sr.py doctor
Требование Значение
Python 3.10 или новее
Файловая система POSIX: Linux, macOS, WSL2
Права не root (иначе нужен --allow-root)
Каталог не /, не /etc, не домашний без явного -C
git или бэкап желательны: правка идёт на месте

Настройка

Что настроить Как
Отбор файлов -p, --include, --exclude, --exclude-dir, --gitignore
Пределы --max-size, --min-size, --max-depth, --newer-than
Режим сопоставления --regex, --ere, --pcre, -i, -w
Хранение журнала --keep N, --keep-age
Конфигурация --config KEY=VALUE, --config-file FILE
Автоматизация --yes для прогонов без терминала
flowchart TD
  R1["не root или --allow-root"] --> G{"все проверки пройдены?"}
  R2["каталог не / и не домашний"] --> G
  R3["есть TTY или --yes"] --> G
  R4["POSIX-файловая система"] --> G
  G -- да --> GO["прогон разрешён"]
  G -- нет --> NO["отказ без записи"]

Про --yes стоит сказать отдельно: без терминала утилита не делает необратимых действий и сообщает об этом. Это правильное поведение — но в cron-скрипте отсутствие флага выглядит как «команда ничего не сделала», а не как ошибка. Именно поэтому --yes требуется указывать явно, а не «по умолчанию соглашаться».

Что показывает журнал сессий

Сессия — это не лог на память, а рабочий инструмент. У неё есть идентификатор, время, число файлов, число замен и текст шаблона; в каталоге .sr лежат данные, достаточные для восстановления содержимого.

Практическая ценность в трёх сценариях.

Первый: разбор через неделю. Вы помните, что «что-то меняли в конфигах», но не помните что. sessions показывает список прогонов с шаблонами — по строке 'old_api' → 'new_api' картина восстанавливается мгновенно.

Второй: откат одного прогона. Обычный git revert отменит все правки коммита, а сессия — ровно свой набор файлов. Это спасает, когда после замены успели сделать другие изменения.

Третий: контроль объёма. sessions size показывает, сколько журнал занимает: у нас на трёх файлах это 7,3 КБ, а на большом дереве счёт пойдёт на мегабайты. По умолчанию хранится двадцать сессий, лишние удаляются автоматически — и это же означает, что откатить правку месячной давности не получится.

Есть и обратная сторона: журнал лежит внутри целевого дерева. Если проект под git, каталог .sr надо добавить в .gitignore — иначе он попадёт в коммит и будет мусорить в диффах.

Почему POSIX — не формальность

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

Утилита сохраняет права и владельца файлов, чтобы правка не меняла метаданные. Для этого она обращается к os.chown — функции, которой в Windows просто нет. Она использует атомарное переименование POSIX, блокировки через O_CREAT|O_EXCL и сохранение inode: без этого «восстановить ровно то, что было» не получится.

Отсюда и наблюдаемое поведение на Windows: поиск, диф и предпросмотр работают, потому что ничего не пишут. А откат, который восстанавливает файлы и затем пытается выставить владельца, падает на первой же попытке — уже после того, как первый файл возвращён.

Правильный вывод не «обойти», а «не запускать запись»: на Windows использовать find и diff, а саму замену делать в WSL2 или на сервере. Кстати, doctor на неподходящей платформе честно предупреждает — 13 проверок показывают, что доступно, а что нет.

Альтернативы и когда они лучше

Честный обзор обязан назвать, чем ещё это делают.

Инструмент Сильная сторона Слабое место
sed -i есть везде, нулевой порог нет предпросмотра и журнала
Замена в редакторе удобный интерфейс, видно контекст не помнит прогон, зависит от машины
git grep + правка встроено в git, быстрый поиск правку и откат делаете руками
Кодмоды (ast-grep, comby) понимают структуру кода, а не текст сложнее настроить, больше зависимостей

sr выигрывает там, где нужна не «умная» правка по синтаксису, а массовая текстовая с проверяемым следом: переименование ключа конфигурации, смена адреса API, нормализация кодировки, переименование файлов по шаблону.

Кодмоды лучше, когда правка должна учитывать структуру: заменить вызов, не тронув одноимённую строку в комментарии. Но за это платят настройкой — и для задачи «переименовать вызов в двухстах файлах» разница обычно не окупается.

Подводные камни

  • Windows не поддерживается для записи. В документации прямо сказано: find и --dry-run работают, путь записи — нет.
  • Откат на Windows падает на os.chown. Проверено: первый файл восстановлен, дальше исключение, дерево в смешанном состоянии.
  • Без --yes прогон без терминала не пишет. Сообщение «assuming ‘no’» легко принять за успех.
  • Журнал лежит в целевом дереве. Каталог .sr надо добавить в .gitignore, иначе он попадёт в коммит.
  • Сессии обрезаются. По умолчанию хранится 20; старые удаляются после каждого прогона, и откатить давнюю правку не получится.
  • Литеральный поиск — не регулярка. Точка и звёздочка в шаблоне означают себя, а не шаблон.
  • Подсчёт по вхождениям, а не по строкам. Одна строка с тремя совпадениями даст три замены.
  • Параллельности нет. Журнал ведётся последовательно намеренно; ускоряются прогоном по каталогам через -C.
  • Лицензия требует сверки. В теге 7.0.1 LICENSE и pyproject.toml говорят MIT, а в AGENTS.md (ветка main) есть замечание о расхождении с PolyForm Noncommercial — перед распространением надо проверять.
  • npm-страница отвечает 403. Это защита от ботов, а не отсутствие пакета; ставить лучше через PyPI или pyz.
  • Права root. Прогон от root отвергается, и обходить это флагом --allow-root стоит только понимая последствия.

Как выглядит безопасный рефакторинг

Общая процедура для любой массовой замены, независимо от инструмента.

Первое: зафиксировать исходное состояние. Коммит в git или копия каталога. Не «я помню, как было», а точка, к которой можно вернуться.

Второе: измерить объём. find даёт число совпадений и файлов; если цифра удивляет — значит, шаблон шире, чем кажется. Частая ошибка — искать короткое имя, которое встречается ещё и в посторонних местах.

Третье: сузить отбор. Расширения, каталоги, исключения. Лучше три узких прогона, чем один широкий: узкий проще проверить и проще откатить по отдельности.

Четвёртое: прочитать диф целиком. Не «пролистать» — прочитать. Диф на двести файлов читается за десять минут и почти всегда показывает одну-две неожиданные правки.

Пятое: выполнить замену и сразу проверить. Сборка, тесты, git diff — что угодно, что даёт объективный сигнал. Если сигнала нет, замена не проверена.

Шестое: записать идентификатор сессии в задачу или в журнал работ. Через месяц он будет единственным способом быстро понять, что произошло.

Седьмое: не смешивать. Если в одном прогоне переименование ключа и нормализация отступов, откат одного без другого невозможен. Одна сессия — одна логическая правка.

Коды выхода и автоматизация

У утилиты есть отдельный документ с контрактом кодов выхода — он заморожен и генерируется из кода, а не пишется руками. Это правильный подход: конвейер опирается на числа, а не на разбор текста сообщений.

Что важно для автоматизации Как
Прогон без терминала обязателен флаг --yes
Машиночитаемый вывод --json или --porcelain
Проверка установки doctor до работы
Проверка обновлений update --check
Отбор файлов те же фильтры, что в find
Хранение журнала --keep, --keep-age

Практическое замечание: в CI массовую замену обычно не запускают — её делают один раз локально и коммитят результат. Утилита полезна именно как инструмент разовой аккуратной правки, а не как постоянный шаг сборки.

Что было бы в статье про версию

Это обзор продукта, а не заметка о релизе, но одно уточнение уместно: 7.0.1 — текущая версия, и в ней закрыты ошибки шестой ветки. Полный список изменений — в CHANGELOG.md репозитория; пересказывать его здесь смысла нет, потому что ценность утилиты не в последней правке, а в модели работы: скан, предпросмотр, журнал, откат.

Чек-лист

  • [ ] Проверить версию Python: 3.10 или новее.
  • [ ] Прогнать doctor и прочитать предупреждения.
  • [ ] Убедиться, что дерево чистое в git или есть бэкап.
  • [ ] Найти совпадения: find с нужным шаблоном и фильтрами.
  • [ ] Посмотреть diff и прочитать его глазами.
  • [ ] Сделать replace --dry-run и сверить число файлов и замен.
  • [ ] Запустить реальную замену и запомнить идентификатор сессии.
  • [ ] Проверить результат: сборка, тесты, git diff.
  • [ ] При проблеме — rollback <id> (на POSIX).
  • [ ] Добавить .sr в .gitignore.
  • [ ] На Windows — не запускать запись, использовать find и diff.

Где утилита не нужна

Границы полезны не меньше достоинств.

Не нужна, когда правка одна. Открыть файл и заменить — быстрее, чем набирать команду.

Не нужна, когда нужна структурная правка. Заменить вызов функции, не тронув одноимённую строку в комментарии, текстовым инструментом не выйдет — здесь нужны кодмоды или ручная работа с пониманием кода.

Не нужна для бинарных файлов: утилита про текст, а не про произвольные данные.

Не нужна как замена системы контроля версий. Журнал сессий — страховка на уровне прогона, а не история проекта; git остаётся обязательным.

И не нужна на Windows для записи. Там её место — поиск и предпросмотр, а сама правка идёт на POSIX-системе.

FAQ

Чем это лучше sed -i?

Предпросмотром (diff, --dry-run), журналом сессий и точечным откатом. sed этого не умеет.

Где хранится журнал?

В каталоге .sr внутри целевого дерева, а не в домашнем каталоге. Старые сессии обрезаются по --keep.

Работает ли на Windows?

Поиск и предпросмотр — да. Запись официально не поддерживается: нужна POSIX-файловая система (Linux, macOS, WSL2).

Что будет без --yes в cron?

Утилита откажется писать и сообщит «not a terminal, assuming ‘no'». Для автоматизации флаг обязателен.

Как откатить не последнюю, а конкретную правку?

sessions покажет список с идентификаторами, rollback <id> откатит нужную.

Нужны ли сторонние пакеты?

Нет: только Python 3.10+. sr.pyz вообще не требует установки.

Можно ли заменить по регулярному выражению?

Да, флагами --regex, --ere или --pcre. По умолчанию сопоставление литеральное.

Что осталось за кадром

Три вещи, которые стоило бы проверить отдельно и которые в этом разборе не проверялись.

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

Вторая: переименование файлов по шаблону. Команда rename в прогоне не участвовала, а у неё свои риски: конфликт имён на регистронезависимых файловых системах, потеря расширений при неаккуратном шаблоне.

Третья: совместимость сессий между версиями. Журнал лежит файлами внутри дерева, и вопрос «прочитает ли новая версия сессию старой» в разборе не закрыт.

Общее правило для читателя: то, что не проверено, надо помечать, а не додумывать. В статье про инструмент это особенно важно — доверие к утилите строится на воспроизводимых прогонах, а не на описании её же документации.

Итог

sr 7.0.1 решает узкую, но частую задачу: массовая правка дерева с возможностью посмотреть, что изменится, и отменить сделанное. Прогон подтвердил основные обещания — 3 совпадения в 3 файлах за 8 мс, предпросмотр на 3 замены, сессия с идентификатором, doctor с 13 проверками без отказов. Границы честные: запись только на POSIX, на Windows откат падает на os.chown, сессии обрезаются, лицензию перед распространением стоит сверить. Для рефакторинга на Linux это заметно спокойнее, чем sed -i, и честнее, чем верить в память.

Цитата: Массовая замена страшна не тем, что меняет много файлов, а тем, что не помнит, какие.

Связанные материалы

LLM Summary

  • Тема: sr 7.0.1 — утилита замены по дереву файлов с журналом сессий и откатом.
  • Команды: find (он же search, grep), diff, replace (r), rename (mv), rollback, sessions, doctor, update --check, completions.
  • Требования: Python 3.10+, POSIX-файловая система (Linux, macOS, WSL2), не root без --allow-root, не системный каталог и не домашний без -C.
  • Безопасность: сопоставление литеральное по умолчанию, регулярки по флагам --regex/--ere/--pcre; запись только после полного скана; без TTY требуется --yes.
  • Журнал: каталог .sr в целевом дереве, сессии обрезаются по --keep (по умолчанию 20) и --keep-age.
  • Проверено 13.11.2026: архив тега — 7 458 640 байт, SHA-256 4081f9a5…a692e84, 116 файлов (18 тестов); артефакты — sr.pyz 495 680 Б, wheel 184 817 Б, sdist 242 704 Б.
  • Прогон: find — 3 совпадения в 3 файлах за 8 мс; diff — «3 файла изменятся, 3 замены»; replace --dry-run — 3 файла / 3 замены / 3 мс; реальная замена — 3 файла / 3 замены / 24 мс, сессия 20261113T030219Z-5b3ed3; sessions size — 7,3 КБ; doctor — 13 проверок, 10 пройдено, 2 предупреждения, 0 отказов.
  • Граница Windows: запись не поддерживается; rollback --yes восстановил первый файл и упал на AttributeError: module 'os' has no attribute 'chown', оставив дерево в смешанном состоянии.
  • Живые адреса: PyPI, GitHub и вики проекта — 200; npm-страница — 403 (защита от ботов).
  • Лицензия: в теге 7.0.1 LICENSE и pyproject.toml — MIT; в AGENTS.md ветки main есть замечание о PolyForm Noncommercial — требует проверки перед распространением.
  • Ссылки: https://github.com/paulmann/sr-search-replace, https://pypi.org/project/sr-search-replace/

Промт для агента (установка и настройка)

Готовый промт: скопируйте блок целиком. Источник шагов — AGENTS.md репозитория (есть в ветке main, в архив тега 7.0.1 не входит) и README.md; сверено с прогоном на живых файлах.

Задача: выполнить массовую замену по дереву файлов утилитой sr так, чтобы результат можно было проверить и откатить.

Материалы:
- репозиторий: https://github.com/paulmann/sr-search-replace
- AGENTS.md: https://github.com/paulmann/sr-search-replace/blob/main/AGENTS.md
- README: https://github.com/paulmann/sr-search-replace/blob/main/README.md
- релиз: https://github.com/paulmann/sr-search-replace/releases/tag/v7.0.1

Шаг 1. Проверить предпосылки: python3 --version (нужен 3.10+), id -u (не root), файловая система POSIX (Linux/macOS/WSL2).
Шаг 2. Установить: скачать sr.pyz и sr.pyz.sha256, проверить сумму, либо pipx install sr-search-replace.
Шаг 3. Прогнать sr doctor и прочитать предупреждения (13 проверок).
Шаг 4. Убедиться, что дерево чистое: git status --short, либо сделать бэкап.
Шаг 5. Найти совпадения: sr find "<шаблон>" -p '<маска>' — оценить число файлов.
Шаг 6. Посмотреть diff и прочитать его: sr diff "<старое>" "<новое>" -p '<маска>'.
Шаг 7. Сделать предпросмотр: sr replace "<старое>" "<новое>" --dry-run — сверить числа.
Шаг 8. Выполнить замену: sr replace "<старое>" "<новое>" -p '<маска>' — записать идентификатор сессии.
Шаг 9. Проверить результат: git diff, сборка, тесты.
Шаг 10. При проблеме откатить: sr rollback <id> (только POSIX; для cron добавить --yes).
Шаг 11. Добавить каталог .sr в .gitignore.

Запрещено:
- запускать запись на Windows: платформа не поддерживается, откат падает на os.chown;
- работать от root без --allow-root;
- запускать замену без предварительного diff и --dry-run;
- рассчитывать на --yes по умолчанию: без TTY прогон не пишет;
- хранить сессии бесконечно: по умолчанию остаётся 20;
- считать шаблон регулярным выражением: по умолчанию сопоставление литеральное;
- распространять копию, не сверив лицензию: в теге MIT, в AGENTS.md упомянут PolyForm.

Что считать успехом: число файлов и замен в --dry-run совпало с ожиданием, реальный прогон дал ту же сводку, идентификатор сессии записан, diff после правки содержит только ожидаемые строки, откат проверен на пробном каталоге.