Глобальный поиск и замена командной строки Linux

Я пытаюсь найти и заменить строку во всех файлах, сопоставленных grep на машине linux. У меня есть некоторые части того, что я хочу сделать, но я не уверен, как лучше всего связать их все вместе.

grep -n 'foo' * даст мне вывод в виде:

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

Для каждого файла, возвращаемого grep, я хотел бы заменить "foo" на "bar" и записать результат обратно в файл. Есть ли хороший способ сделать это? Может быть, фантастический трубопровод?

Ответ 1

Вы хотите найти и заменить строку во всех файлах, соответствующих grep?

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

Edit

Так как это, кажется, довольно популярный вопрос, который я бы обновил.

В настоящее время я в основном использую 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

Ответ 2

Это похоже на то, что вы хотите, на основе приведенного вами примера:

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 для поиска - это удобство, чтобы показать, какие файлы были сопоставлены. Все это зависит от точных версий этих инструментов в вашей системе.

Ответ 3

Если ваш 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/' *

Ответ 4

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

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

Я предполагаю, что с '*.htm?' вместо .html он ищет и находит файлы .htm и .html.

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

Ответ 5

Это работает с использованием grep без использования perl или find.

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

Ответ 6

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

Ответ 7

Ответ, уже использованный при использовании 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 script Я написал некоторое время назад для некоторых более сложных задач переименования и рефакторинга. Причины, по которым вы, возможно, предпочтете это:

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

Проверьте пример README.

Ответ 8

Это на самом деле проще, чем кажется.

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) в файле (опять же, представленным%)

Легкий peasy. Если вы хорошо разбираетесь в поиске, grep, xargs, sed и awk, почти ничего не получается, когда дело касается обработки текстовых файлов в bash:)