Командний рядок: пошук і заміна у всіх назвах файлів, що відповідають grep


80

Я намагаюся знайти та замінити рядок у всіх файлах, що відповідають grep:

grep -n 'foo' * дасть мені результат у вигляді:

[filename]:[line number]:[text]

Для кожного файлу, що повертається grep, я хотів би змінити файл, замінивши fooна bar.

Відповіді:


70

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

perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

Редагувати

Оскільки це, здається, досить популярне питання, яке я думав би оновити.

Сьогодні я в основному використовую, ack-grepоскільки це більш зручно для користувачів. Отже, наведена вище команда буде такою:

perl -p -i -e 's/old/new/g' `ack -l searchpattern`

Для обробки пробілів в іменах файлів ви можете запустити:

ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

ви можете зробити більше ack-grep. Скажімо, ви хочете обмежити пошук лише файлами HTML:

ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

І якщо пробіли не є проблемою, це ще коротше:

perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files

3
Я думаю, що це може мати проблеми з файлами / папками, які містять пробіли. Can't open Untitled: No such file or directory, <> line 5при спробі "Без назви папка / файл.txt".
Xeoncross

108

Здається, це те, що ви хочете, виходячи з прикладу, який ви дали:

sed -i 's/foo/bar/g' *

Він не є рекурсивним (він не потраплятиме в підкаталоги). Для хорошого рішення заміни вибраних файлів у дереві я б використав find:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Це *.htmlвираз, якому повинні збігатися файли, .bakпісля -iстворення копії оригінального файлу з розширенням .bak (це може бути будь-яке розширення, яке вам подобається), а gв кінці виразу sed вказує sed замінити кілька копій на один рядок (а не лише перший). -printЗнайти це зручність для показу , які файли були узгоджені. Все це залежить від точних версій цих інструментів у вашій системі.


1
Слово попередження для користувачів cygwin. find і sed combo, здається, змінює права користувача на файли, які передаються через. Це можна просто виправити, скориставшись командою chmod -R 644 * з того самого рівня, що і при використанні функції find / sed.
kaskelotti

Слово попередження людям, які не хочуть використовувати аргумент -i: якщо ви не використовуєте його, він не працює (не питайте мене, чому)
knocte

4
@knocte -i вказує sed змінити файл, інакше він просто друкує змінену версію до stdout. Якщо ви не хочете, щоб файл .bak був створений, просто опустіть частину .bak, -i теж працює автономно.
MattJ

2
На OSX вам потрібно дати findкоманді каталог для запуску, наприклад find . -name '*.html'або find directoryname/ -name '*'.
Michiel Kauw-A-Tjoe

мені потрібно було додати -e, інакше він вважав, що частина 's ...' - це суфіксsed -ie 's/foo/bar/g' *
vish

14

Якщо у вас sed(1)є -iможливість, використовуйте її так:

for i in *; do
  sed -i 's/foo/bar/' $i
done

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

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *

2
for i in *; do ...є зайвим, sed може взяти список файлів як аргументи.
Йенс

2
@Jens, чому б не покращити цю відповідь, додавши власний приклад до наведеного вище?
Сорока

Завжди слід вказувати змінніfor i in *; do; sed -i 's/foo/bar/g' "$i"; done
кіт

6

Мені подобається та використовується вищезазначене рішення або загальносистемний пошук та заміна серед тисяч файлів:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

Я припускаю, що з '* .htm?' замість .html здійснює пошук і знаходження файлів .htm та .html однаково.

Я замінюю .bak на більш загальносистемну тильду (~), щоб полегшити очищення файлів резервних копій.


4

Це працює за допомогою grep без необхідності використовувати perl або find.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

xargs не має -i на OSX або BSD openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/ ... ви мали на увазі використовувати велику літеру "I"?
Тоні Адамс,

Я не знав, -iщо не працює для інших ОС. Працює у мене на ubuntu.
pymarco

Моя сторінка xargs(на Ubuntu) каже, що -iзастаріла та використовується -Iзамість неї. Отже, слід сказати:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath
Макс Уоллес

3

find . -type f -print0 | xargs -0 <sed/perl/ruby cmd>буде обробляти кілька імен файлів, що містять пробіл, одночасно завантажуючи по одному інтерпретатору за партію. Набагато швидше.


@knocte, "cmd" - це маркер для всієї команди пошуку та заміни для будь-якого з вибраних інструментів. Ця відповідь відповідає на питання про те, як поводитися з пробілами, що містяться в іменах файлів.
Тоні Адамс,

1

Відповідь, вже дана за допомогою find та sed

find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

це, мабуть, стандартна відповідь. Або ви можете використовувати perl -pi -e s/foo/bar/g'замість sedкоманди.

Для більшості швидких застосувань команда rpl легше запам'ятовується. Ось заміна (foo -> bar), рекурсивно для всіх файлів у поточному каталозі:

rpl -R foo bar .

Він не доступний за замовчуванням у більшості дистрибутивів Linux, але швидко встановлюється ( apt-get install rplабо подібний).

Однак для більш жорстких завдань, які передбачають регулярні вирази та зворотну заміну, або перейменування файлів, а також пошук та заміну, найзагальнішим та найпотужнішим інструментом, який я знаю, є repren , невеликий скрипт Python, який я писав деякий час тому для деяких торнірські завдання перейменування та рефакторингу. Причини, за якими ви можете віддати перевагу:

  • Підтримка перейменування файлів, а також пошук і заміна вмісту файлів (включаючи переміщення файлів між каталогами та створення нових батьківських каталогів).
  • Перегляньте зміни, перш ніж виконувати пошук та заміну.
  • Підтримка регулярних виразів із заднім заміщенням, цілими словами, без урахування регістру та збереження регістру (замінити foo -> bar, Foo -> Bar, FOO -> BAR).
  • Працює з кількома замінами, включаючи свопи (foo -> bar і bar -> foo) або набори не унікальних замін (foo -> bar, f -> x).

Перевірте README для прикладів.


1

Це насправді простіше, ніж здається.

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep повторюється через ваше дерево (-R) і друкує лише ім'я файлу (-l), починаючи з поточного каталогу (./)
  • що потрапляє до xargs, який обробляє їх по черзі (-n 1) і використовує% як заповнювач (-I%) у команді оболонки (sh -c)
  • в команді оболонки спочатку друкується ім'я файлу (ls%;)
  • тоді sed виконує вбудовану операцію (-i), підстанцію ('s /') foo зі стовпчиком (foo / bar), глобально (/ g) у файлі (знову ж таки, представлене%)

Простенька. Якщо ви добре зрозумієте find, grep, xargs, sed та awk, майже нічого неможливого не стосується маніпуляцій з текстовими файлами в bash :)


Бах, ігноруйте, це пімарко вже висвітлювало це вище. Залишаючи свою відповідь для пояснення.
siliconrockstar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.