Как я могу сделать рекурсивный поиск и замену из командной строки?

84

Используя оболочку, такую ​​как bash или zshell, как я могу сделать рекурсивный поиск и замену? Другими словами, я хочу заменить каждое вхождение 'foo' на 'bar' во всех файлах в этом каталоге и его подкаталогах.

Натан Лонг
источник
Альтернативный ответ на те же вопросы можно найти здесь stackoverflow.com/questions/9704020/…
dunxd
Это может быть хорошей идеей, чтобы попробовать это в VIM. Таким образом, вы можете использовать функцию подтверждения, чтобы убедиться, что вы не поменяете местами то, что вам не нужно. Я не уверен, что это можно сделать в каталоге.
Сэми Бенчериф

Ответы:

106

Эта команда сделает это (протестировано на Mac OS X Lion и Kubuntu Linux).

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Вот как это работает:

  1. find . -type f -name '*.txt'находит в текущем каталоге ( .) и ниже все обычные файлы ( -type f), имена которых заканчиваются на.txt
  2. | передает вывод этой команды (список имен файлов) следующей команде
  3. xargs собирает эти имена и передает их один за другим sed
  4. sed -i '' -e 's/foo/bar/g'означает «отредактировать файл на месте, без резервной копии, и выполнить следующую подстановку ( s/foo/bar) несколько раз в строке ( /g)» (см. man sed)

Обратите внимание, что часть «без резервной копии» в строке 4 подходит для меня, потому что файлы, которые я изменяю, в любом случае находятся под контролем версий, поэтому я могу легко отменить, если произошла ошибка.

Натан Лонг
источник
9
Никогда не труба находит вывод в xargs без -print0опции. Ваша команда не будет работать с файлами с пробелами и т.д. в их имени.
slhck
20
Также просто find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +сделаю все это с помощью GNU find.
Даниэль Андерссон
6
Я получаю, sed: can't read : No such file or directoryкогда я бегу find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', но find . -name '*.md' -print0дает список многих файлов.
Мартин Тома
9
Это -i''
сработает
3
В чем смысл ''после того, sed -iкакова ''роль?
Jas
27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

Это удаляет xargsзависимость.

Ztyx
источник
4
Это не работает с GNU sed, поэтому не работает на большинстве систем. GNU sedтребует от вас не ставить пробел между -iи ''.
Slhck
1
Принятые ответы лучше объясняют это. но +1 за использование правильного синтаксиса.
Ордбен
9

Если вы используете Git, вы можете сделать это:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lперечисляет только имена файлов. -zпечатает нулевой байт после каждого результата.

Я закончил тем, что сделал это, потому что у некоторых файлов в проекте не было новой строки в конце файла, и sed добавил новую строку, даже когда это не сделало никаких других изменений. (Нет комментариев о том, должны ли файлы иметь перевод строки в конце. 🙂)

Дэвид Винецки
источник
1
Большой +1 за это решение. Остальные find ... -print0 | xargs -0 sed ...решения не только занимают больше времени, но и добавляют новые строки в файлы, у которых их еще нет, что создает неудобства при работе внутри git-репо. git grepсветится быстро по сравнению
Джош Купершмидт
3

Вот моя функция zsh / perl, которую я использую для этого:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

И я бы выполнил это с помощью

$ change foo bar **/*.java

(например)

Брайан Агнью
источник
2

Пытаться:

sed -i 's/foo/bar/g' $(find . -type f)

Проверено на Ubuntu 12.04.

Эта команда НЕ будет работать, если имена подкаталогов и / или имена файлов содержат пробелы, но если они у вас есть, не используйте эту команду, так как она не будет работать.

Обычно плохая практика - использовать пробелы в именах каталогов и именах файлов.

http://linuxcommand.org/lc3_lts0020.php

Посмотрите на "Важные факты об именах файлов"

nexayq
источник
Попробуйте, когда у вас есть файл (ы) с пробелом (ами) в их именах. (Существует эмпирическое правило, которое гласит: «Если что-то кажется слишком хорошим, чтобы быть правдой, то, вероятно, так оно и есть». Если вы «обнаружили» решение, более компактное, чем то, что кто-либо другой опубликовал за 3,5 года, вы должны спросить себя, почему это может быть.)
Скотт
1

Используйте этот сценарий оболочки

Теперь я использую этот сценарий оболочки, который объединяет вещи, которые я узнал из других ответов и поиска в Интернете. Я поместил это в файл, названный changeв папке на моем $PATHи сделал chmod +x change.

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done
Натан Лонг
источник
0

В моем случае я хотел заменить foo:/Drive_Letterна foo:/bar/baz/xyz В моем случае я смог сделать это с помощью следующего кода. Я был в той же папке, где было много файлов.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

надеюсь, что это помогло.

neo7
источник
0

Следующая команда отлично работала на Ubuntu и CentOS; Тем не менее, под OS XI постоянно получал ошибки:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": неверный код команды.

Когда я пытался передать параметры через xargs, все работало без ошибок:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'
Джон Морган
источник
Тот факт , что вы изменили , -iчтобы -i '', вероятно , более актуальным , чем тот факт , что вы изменили -execв -print0 | xargs -0. Кстати, вам, вероятно, не нужно -e.
Скотт
0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Вышеописанное работает как шарм, но со связанными каталогами, я должен добавить -Lфлаг к нему. Финальная версия выглядит так:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
sMajeed
источник