Как выполнить рекурсивный поиск/замену строки с помощью awk или sed?
Как мне найти и заменить каждое вхождение:
subdomainA.example.com
на
subdomainB.example.com
в каждом текстовом файле в каталоге /home/www/
и всех его подкаталогах рекурсивно?
5 ответ(ов)
Команда, которую вы представили, выглядит следующим образом:
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'
Команда -print0
указывает find
выводить каждый результат, разделяя их нулевым символом, а не символом новой строки. Это позволяет xargs
корректно обрабатывать имена файлов даже в случае, если в них присутствуют символы новой строки.
Выражение \( -type d -name .git -prune \)
полностью пропускает все директории с именем .git
. Вы можете легко расширить это выражение, если используете SVN или у вас есть другие директории, которые вы хотите исключить — просто добавьте дополнительные имена для сопоставления. Это примерно эквивалентно -not -path .git
, но более эффективно, так как вместо проверки каждого файла в директории, оно просто пропускает её целиком. Один из важнейших моментов — это то, что -o
(или) после этого выражения требуется из-за особенностей работы -prune
.
Для получения дополнительной информации вы можете обратиться к документации, выполнив man find
.
Самый простой способ, по моему мнению, выглядит так:
grep -rlZ oldtext . | xargs -0 sed -i 's/oldtext/newtext/g'
Этот командный набор ищет рекурсивно все файлы в текущей директории (и подкаталогах), содержащие oldtext
, и заменяет его на newtext
во всех найденных файлах. Параметр -r
указывает на рекурсивный поиск, -l
выводит только имена файлов, а -Z
обеспечивает корректную обработку файлов с пробелами в именах. Использование xargs -0
гарантирует, что имена файлов будут правильно переданы в sed
.
Примечание: Не выполняйте эту команду в папке, содержащей репозиторий Git, так как изменения в .git могут повредить ваш индекс Git.
find /home/www/ -type f -exec \
sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +
По сравнению с другими ответами, данный подход проще и использует sed
, как и запрашивалось в оригинальном вопросе, вместо perl
.
Всё это похоже на другие приемы, но мне нравится этот:
find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
find <mydir>
: ищет в указанной директории.-type f
:
Файл является обычным файлом.
-exec command {} +
:
Этот вариант действия -exec выполняет указанную команду для выбранных файлов, но команда формируется путем добавления каждого выбранного имени файла в конец; общее количество вызовов команды будет гораздо меньше, чем количество совпадающих файлов. Командная строка формируется аналогично тому, как xargs создает свои командные строки. Разрешено только одно вхождение
{}'
в команде. Команда выполняется в исходной директории.
Самое простое решение, которое помогает мне его запомнить, это ссылка на ответ на StackOverflow: https://stackoverflow.com/a/2113224/565525, а именно:
sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)
ПРИМЕЧАНИЕ: -i ''
решает проблему с OSX: sed: 1: "...": invalid command code .
ПРИМЕЧАНИЕ: Если файлов для обработки слишком много, вы получите ошибку Argument list too long
. В качестве обходного решения используйте find -exec
или xargs
, как описано выше.
Bash инструмент для получения n-й строки из файла
Извлечение имени файла и расширения в Bash
Как сделать паузу в shell-скрипте на одну секунду перед продолжением?
Как работает "cat << EOF" в bash?
Передача параметров в функцию Bash