Використовуючи оболонку типу 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 знаходкою.
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 repo. 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'