Як грепнути і замінити


252

Мені потрібно рекурсивно шукати вказаний рядок у всіх файлах і підкаталогах у каталозі та замінювати цей рядок іншим рядком.

Я знаю, що команда знайти це може виглядати приблизно так:

grep 'string_to_find' -r ./*

Але як я можу замінити кожен екземпляр string_to_findіншим рядком?


Я не вірю, що греп може це зробити (я можу помилитися). Найпростішими способами буде використання sed або perl для заміни
Memento Mori

2
Спробуйте використовуватиsed -i 's/.*substring.*/replace/'
Eddy_Em

2
@Eddy_Em Це замінить весь рядок заміною. Вам потрібно використовувати групування, щоб захопити частину рядка до і після підрядка, а потім помістити її в рядок заміни. sed -i 's/\(.*\)substring\(.*\)/\1replace\2/'
JStrahl


Відповіді:


249

Інший варіант - скористатися знахідкою, а потім передати її через sed.

find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;

34
На терміналі OS X 10.10 потрібен належний рядок розширення до параметра -i. Наприклад, у find /path/to/files -type f -exec sed -i "" "s/oldstring/new string/g" {} \;будь-якому випадку надання порожнього рядка все ще створює файл резервної копії на відміну від описаного в посібнику ...
Eonil

10
Чому я отримую "sed: RE помилка: незаконна послідовність байтів". І так, я додав -i ""для OS X. Це працює інакше.
тако

2
У мене виник незаконний випуск послідовності байтів на macOS 10.12, і це питання / відповідь вирішило мою проблему: stackoverflow.com/questions/19242275/… .
abeboparebop

3
Це стосується кожного файлу, щоб змінити час файлу; і перетворює закінчення рядків з CRLFу LFв Windows.
jww

183

Я отримав відповідь.

grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'

14
Це сканувало б відповідні файли двічі ... один раз, grepа потім знову за допомогою sed. Використання findметоду є більш ефективним, але цей метод, який ви згадуєте, працює.
cmevoli

41
Для OS X вам потрібно буде змінити, sed -i 's/str1/str2/g'щоб sed -i "" 's/str1/str2/g'це працювало.
jdf

6
@cmevoli за допомогою цього методу grepпроходить усі файли та sedсканує лише ті файли, на які погодиться grep. За допомогою findметоду в іншій відповіді findспочатку перераховані всі файли, а потім sedбуде проскановано всі файли в цьому каталозі. Тому цей метод не обов'язково повільніше, це залежить від того, скільки матчів є і відмінність в швидкості пошуку між sed, grepі find.
joelostblom

4
ОТО дозволяє таким чином ПОТРІБНУВАТИ, що виявляє греп перед тим, як насправді замінити, значно зменшивши ризик виходу з ладу, особливо для регексу n00bs, як я
Lennart Rolland,

2
Це також корисно, коли ваша заміна grep розумніша, ніж sed. Наприклад, ripgrep підкоряється .gitignore, поки sed не робить.
користувач31389

44

Ви навіть можете зробити це так:

Приклад

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Це дозволить шукати рядок ' windows ' у всіх файлах відносно поточного каталогу та замінити ' windows ' на ' linux ' для кожного появи рядка у кожному файлі.


2
Це grepкорисно лише за наявності файлів, які не слід змінювати. Запуск sedусіх файлів оновить дату зміни файлу, але вміст залишиться незмінним, якщо немає відповідностей.
трійка

@tripleee: Будьте обережні з ... але [sed] залишайте вміст незмінним, якщо немає відповідностей " . Під час використання -iя вважаю, що sedзмінює час файлу кожного файлу, який він торкається, навіть якщо вміст є незмінним. sedТакож перетворює закінчення рядків . Я не використовую sedна Windows , в репо Git , тому що все CRLFзмінюється на LF.
jww

Ця команда потребує знаку "" після -i, щоб позначати, що жодні файли резервного копіювання не відбудуться після того, як відбувається заміна на місці, принаймні в macosx. Перевірте детальну сторінку на деталі. Якщо ви хочете створити резервну копію, саме тут ви помістите розширення файлу, який потрібно створити.
spinyBabbler

31

Для мене це найкраще працює в OS X:

grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi

Джерело: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files


це прекрасно! також працює з ag:ag "search" -l -r . | sort | uniq | xargs perl -e 's/search/replace' -pi

@sebastiankeller У вашій команді Perl бракує остаточної косої риски, що є синтаксичною помилкою.
трійка

3
Чому sort -uрівна частина цього? За яких обставин ви розраховуєте grep -rlстворити одне і те ж ім’я файлу двічі?
tripleee

5

Інші рішення змішують синтаксиси регулярних виразів. Щоб використовувати шаблони perl / PCRE як для пошуку, так і для заміни та обробки лише відповідних файлів, це працює досить добре:

grep -rlZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'

де match1і match2зазвичай ідентичні, але match1можуть бути спрощені для видалення більш вдосконалених функцій, які стосуються лише підстановки, наприклад, групи захоплення.

Переклад: grepрекурсивно та список файлів, що відповідають цьому шаблону PCRE, розділені нулем для захисту будь-яких спеціальних символів у імені файлу, а потім передайте ті назви файлів, до xargsяких очікує розділений за нулем список, але нічого не зробить, якщо не буде отримано імен, і дістатися perlдо ліній-замінників, де знайдено відповідність.

Додайте Iперемикач, grepщоб ігнорувати бінарні файли. Для відповідності регістру відмітьте iперемикач grepі iпрапор, приєднаний до виразу заміни, але не сам iперемикач perl.


Сам Perl цілком здатний повторювати структуру файлів. Насправді є інструмент, find2perlякий постачається з Perl, який робить подібні речі без жодних xargsхитрощів.
tripleee

@tripleee findне шукає вміст файлів, а справа в тому, щоб обробляти файли, що відповідають файлам, не записуючи програму Perl.
Вальф

Це гарне рішення для Windows, оскільки це дозволяє уникнути проблеми перетворення рішень на основі сед. Дякую!
JamHandy

4

Зазвичай не з грепом, а скоріше з sed -i 's/string_to_find/another_string/g'або perl -i.bak -pe 's/string_to_find/another_string/g'.


3

Будьте дуже обережні при використанні findта sedв git repo! Якщо ви не виключаєте бінарних файлів, ви можете довести цю помилку:

error: bad index file sha1 signature 
fatal: index file corrupt

Щоб вирішити цю помилку, потрібно скасувати її sed, замінивши свою new_stringна свою old_string. Це відновить замінені рядки, тому ви повернетесь до початку проблеми.

Правильний спосіб пошуку рядка та його заміни - це пропуск findта використання grepнатомість для ігнорування бінарних файлів:

sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")

Кредити для @hobs


1

Ось що я б робив:

find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

це буде шукати всі файли, що містяться filenameв імені файлу під /path/to/dir, ніж для кожного знайденого файлу, шукати рядок з searchstringі замінювати oldна new.

Хоча, якщо ви хочете пропустити пошук певного файлу з filenameрядком у назві файлу, просто зробіть це:

find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'

Це буде робити те саме, що вище, але для всіх файлів, знайдених під /path/to/dir.


0

Іншим варіантом буде просто використовувати perl з globstar.

Включення shopt -s globstarв ваш .bashrc(або де) дозволяє **шаблон Глоб , щоб відповідати все підкаталоги і файли рекурсивно.

Таким чином використання perl -pXe 's/SEARCH/REPLACE/g' -i **рекурсивно замінить SEARCHна REPLACE.

-XПрапор говорить Perl , щоб «відключити всі попередження» - це означає , що він не буде скаржитися на каталоги.

Globstar також дозволяє робити такі речі , як , sed -i 's/SEARCH/REPLACE/g' **/*.extякщо ви хочете замінити SEARCHз REPLACEу всіх дочірніх файлах з розширенням .ext.


"Іншим варіантом було б просто використовувати perl з globstar ..." - Не на машинах Posixy, як Solaris. Ось чому я спеціально шукаю grepі sed.
jww
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.