Команда — grep -rl 'old' . | xargs sed -i 's/old/new/g'. Важная половина здесь первая: grep -rl показывает, какие файлы изменятся, и вы успеваете посмотреть до того, как sed перепишет их на месте.
1. Какие файлы вообще затронет?
$ clai покажи файлы внутри proj, в которых встречается old_name→ grep -rl 'old_name' proj/proj/src/b.pyproj/src/a.py
-r идёт вглубь, -l печатает имена файлов вместо совпавших строк. Два файла — правдоподобное число. Если написало 4000, шаблон слишком широкий, и вы только что сэкономили себе полдня.
2. Заменить ровно в этих файлах
$ clai замени old_name на new_name во всех файлах внутри proj, где оно есть→ grep -rl 'old_name' proj/ | xargs sed -i 's/old_name/new_name/g'$ cat proj/src/a.pynew_name = 1
Открываются только совпавшие файлы. Если вместо этого пустить sed -i по всему дереву, вы перепишете время изменения у каждого файла репозитория, включая тысячи не изменившихся.
3. Пробелы в именах файлов ничего не сломают
$ clai та же замена, но чтобы корректно работала с пробелами в именах→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i 's/old_name/new_name/g'
-Z заставляет grep разделять имена нулевым байтом, а -0 — xargs этого ожидать. Без этой пары my file.py приезжает двумя аргументами, и sed не правит ни один. -r не даёт xargs запустить sed вообще без аргументов, если совпадений не нашлось.
4. Обойти каталоги, которые трогать нельзя
$ clai замени, но пропусти node_modules, .git и vendor→ grep -rlZ --exclude-dir={.git,node_modules,vendor} 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'
Без исключений вы рано или поздно перепишете исходники зависимости, и дифф будет на 40 000 строк.
5. Только один тип файлов
$ clai замени только в python-файлах→ grep -rlZ --include='*.py' 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'
--include — это белый список, близнец --exclude. Чем уже, тем безопаснее.
6. Сделать бэкап — и остаться переносимым
$ clai сделай замену с бэкапом каждого файла, чтобы работало и на маке→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i.bak 's/old_name/new_name/g'
sed -i.bak кладёт a.py.bak рядом с каждым изменённым файлом. Это единственный вариант написания, который работает и в GNU, и в BSD: macOS требует суффикс, Linux его принимает.
Грабли
- Ваш шаблон — регулярка, а не строка.
.,*,/и$особенные. Заменаconfig.ymlпопадёт и вconfigXyml; экранируйте или возьмите разделитель, которого нет в тексте:sed -i 's|old/path|new/path|g'. - Сначала коммит. У
sed -iнет отмены. Чистый git status до замены превращает ошибку вgit checkout .. grep -lостанавливается на первом совпадении в файле — за счёт этого он быстрый. На замену это не влияет:sedвсё равно проходит все строки.
Похожие вопросы
Как посмотреть замену, не применяя её? Уберите -i и дайте напечатать: grep -rlZ 'old' . | xargs -0 sed 's/old/new/g' | less. Или grep -rn 'old' ., чтобы увидеть каждую строку с номером.
Почему ничего не произошло? Обычно xargs получил пустой список либо шаблон совпадает не с тем, что вы думаете. Запустите отдельно половину с grep -rl.
Можно то же самое через find? Да: find . -type f -name '*.py' -exec sed -i 's/old/new/g' {} +. Но он перепишет каждый подошедший по имени файл независимо от того, было ли в нём совпадение, — поэтому вариант с grep -rl предпочтительнее.
Читайте также
- Переименовать сотни файлов: от циклов
forк обычным словам - sed -i не работает на Mac? Различия BSD и GNU
- От однострочника к закоммиченному скрипту: когда пора
Опишите замену, прочитайте команду, которую напишет CliAI, и запустите. Установка одной строкой.