Используя оболочку типа bash или zshell, как я могу сделать рекурсивный поиск и замену? Другими словами, я хочу заменить каждое вхождение 'foo' на 'bar' во всех файлах в этом каталоге и его подкаталогах.
8 ответов
Эта команда сделает это (протестировано на 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'
Вот как это работает:
find . -type f -name '*.txt'
находит в текущем каталоге (.
) и ниже все обычные файлы (-type f
), имена которых заканчиваются на.txt
|
передает результат этой команды (список имен файлов) следующей командеxargs
собирает эти имена и передает их один за другим вsed
sed -i '' -e 's/foo/bar/g'
означает «отредактировать файл на месте, без резервной копии, и выполнить следующую подстановку (s/foo/bar
) несколько раз в строке (/g
)» (смотриman sed
)
Обратите внимание, что часть «без резервной копии» в строке 4 - это нормально для меня, потому что файлы, которые я изменяю, в любом случае находятся под контролем версий, поэтому я могу легко отменить, если произошла ошибка.
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Это удаляет зависимость xargs
.
Если вы используете Git, вы можете сделать это:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
перечисляет только имена файлов. -z
печатает нулевой байт после каждого результата.
Я закончил тем, что сделал это, потому что у некоторых файлов в проекте не было новой строки в конце файла, и sed добавил новую строку, даже когда это не сделало никаких других изменений. (Нет комментариев о том, должны ли файлы иметь перевод строки в конце. )
Вот моя функция 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
(например)
Пытаться:
sed -i 's/foo/bar/g' $(find . -type f)
Проверено на Ubuntu 12.04.
Эта команда НЕ будет работать, если имена подкаталогов и / или имена файлов содержат пробелы, но если они у вас есть, не используйте эту команду, так как она не будет работать.
Обычно плохая практика - использовать пробелы в именах каталогов и именах файлов.
http://linuxcommand.org/lc3_lts0020.php
Посмотрите на "Важные факты об именах файлов"
Используйте этот сценарий оболочки
Теперь я использую этот сценарий оболочки, который объединяет вещи, которые я узнал из других ответов и поиска в Интернете. Я поместил его в файл с именем 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
В моем случае я хотел заменить 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'
надеюсь, что это помогло.
Следующая команда отлично работала на Ubuntu и CentOS; Тем не менее, под OS XI продолжал получать ошибки:
find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;
Сед: 1: "./Root ": неверный код команды.
Когда я пытался передать параметры через xargs, все работало нормально, без ошибок:
find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'