Используя оболочку, такую как bash или zshell, как я могу сделать рекурсивный поиск и замену? Другими словами, я хочу заменить каждое вхождение 'foo' на 'bar' во всех файлах в этом каталоге и его подкаталогах.
Используя оболочку, такую как bash или zshell, как я могу сделать рекурсивный поиск и замену? Другими словами, я хочу заменить каждое вхождение 'foo' на 'bar' во всех файлах в этом каталоге и его подкаталогах.
Ответы:
Эта команда сделает это (протестировано на 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 подходит для меня, потому что файлы, которые я изменяю, в любом случае находятся под контролем версий, поэтому я могу легко отменить, если произошла ошибка.
-print0
опции. Ваша команда не будет работать с файлами с пробелами и т.д. в их имени.
find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +
сделаю все это с помощью GNU find.
sed: can't read : No such file or directory
когда я бегу find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'
, но find . -name '*.md' -print0
дает список многих файлов.
-i
''
''
после того, sed -i
какова ''
роль?
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +
Это удаляет xargs
зависимость.
sed
, поэтому не работает на большинстве систем. GNU sed
требует от вас не ставить пробел между -i
и ''
.
Если вы используете Git, вы можете сделать это:
git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'
-l
перечисляет только имена файлов. -z
печатает нулевой байт после каждого результата.
Я закончил тем, что сделал это, потому что у некоторых файлов в проекте не было новой строки в конце файла, и sed добавил новую строку, даже когда это не сделало никаких других изменений. (Нет комментариев о том, должны ли файлы иметь перевод строки в конце. 🙂)
find ... -print0 | xargs -0 sed ...
решения не только занимают больше времени, но и добавляют новые строки в файлы, у которых их еще нет, что создает неудобства при работе внутри git-репо. git grep
светится быстро по сравнению
Вот моя функция 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/' {} \;
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
.
# 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'