Как сделать рекурсивный поиск / замену строки с помощью awk или sed?

465

Как найти и заменить каждое вхождение:

subdomainA.example.com

с

subdomainB.example.com

в каждом текстовом файле в дереве каталогов /home/www/ (рекурсивный поиск/замена).

  • 84
    Совет: не делайте ниже в дереве проверки svn ... это перезапишет волшебные файлы папки .svn.
  • 5
    Боже мой, это именно то, что я только что сделал. Но это сработало и, похоже, не принесло никакого вреда. Что самое худшее, что могло случиться?
Показать ещё 5 комментариев
Теги:
awk
replace
sed

29 ответов

601

Примечание. Не запускайте эту команду в папке, содержащей git repo - изменения в .git могут повредить ваш индекс git.

find /home/www -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

От man find:

-print0 (только для поиска GNU) сообщает find использовать пустой символ (\ 0) вместо пробела в качестве разделителя вывода между найденными именами путей. Это более безопасный вариант, если файлы могут содержать пробелы или другой специальный символ. Рекомендуется использовать аргумент -print0 для поиска, если вы используете команду -exec или xargs (аргумент -0 необходим в xargs.).

  • 122
    В OSX вы можете встретить sed: 1: "...": invalid command code . проблема. Кажется, опция -i ожидает расширения и разбирает команду 's/../...' . Решение: передайте расширение '' в опцию -i, например, sed -i '' 's/...
  • 5
    Примечание: если вы используете это в каталоге и удивляетесь, почему svn st показывает изменений, это потому, что вы также изменили файлы в каталогах .svn! Используйте find . -maxdepth 1 -type f -print0 | xargs -0 sed -i 's/toreplace/replaced/g' .
Показать ещё 24 комментария
189

Примечание. Не запускайте эту команду в папке, содержащей git repo - изменения в .git могут повредить ваш индекс git.

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

По сравнению с другими ответами здесь это проще, чем у большинства, и использует sed вместо perl, что и требовался исходный вопрос.

  • 46
    Обратите внимание, что если вы используете BSD sed (в том числе в Mac OS X), вам нужно будет указать явную пустую строку arg для опции sed -i . т.е.: sed -i '' 's/original/replacement/g'
  • 1
    @DanCarley Нет, это менее эффективно, чем решение xargs Никиты из-за разветвления
Показать ещё 12 комментариев
42

Все трюки почти одинаковы, но мне нравится этот:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>: найдите в каталоге.

  • -type f:

    Файл имеет тип: обычный файл

  • -exec command {} +:

    Этот вариант действия -exec выполняет указанную команду в выбранных файлах, но командная строка создается путем добавления           каждое имя выбранного файла в конце; общее количество вызовов команды будет намного меньше, чем количество           согласованные файлы. Командная строка построена почти так же, как xargs создает свои командные строки. Только один экземпляр           `{} 'разрешено внутри команды. Команда запускается в стартовом каталоге.

  • 0
    @ user2284570 с -exec? Попробуйте установить путь к исполняемому файлу вместо имени инструмента.
  • 0
    @ I159: Нет: исключить исполняемые файлы (но включают сценарии оболочки) .
Показать ещё 3 комментария
36
cd /home/www && find . -type f -print0 |
  xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'
  • 0
    возможно, поставьте обратную косую черту перед точками в первой половине подстановки perl s ///, но это вряд ли имеет значение.
  • 2
    Мне любопытно, есть ли причина использовать -print0 и xargs вместо -exec или -execdir ?
Показать ещё 5 комментариев
30

Самый простой способ для меня -

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'
  • 0
    @Anatoly: только один вопрос: как я могу исключить двоичные файлы (исполняемые файлы) ?
  • 2
    @ user2284570 Используйте -I или --binary-file=without-match флаги grep --binary-file=without-match .
Показать ещё 3 комментария
26

Для меня самое легкое решение для запоминания - https://stackoverflow.com/questions/2113111/how-can-i-search-and-replace-recursively-in-a-directory-in-vim, то есть:

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 решение, описанное выше.

  • 4
    workaround должен быть предпочтительным синтаксисом во всех случаях.
  • 1
    Проблема с подстановкой команд $(find...) заключается в том, что оболочка не может обработать имена файлов с пробелами или другими метасимволами оболочки. Если вы знаете, что это не проблема, такой подход хорош; но у нас слишком много вопросов, по которым люди не были предупреждены об этой проблеме или не поняли предупреждение.
15

Для тех, кто использует серебряный искатель (ag)

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

Так как ag игнорирует файл git/hg/svn файл/папки по умолчанию, это безопасно запускать внутри репозитория.

12

Один приятный oneliner как дополнительный. Использование git grep.

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"
  • 3
    Хорошая идея, если вы работаете в git-репо, так как вы не рискуете переписать .git / content (как указано в комментариях к другому ответу).
9

Этот совместим с репозиториями git и немного проще:

Linux:

git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'

Mac:

git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'

(Благодаря http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/)

  • 0
    xargs -0 опцию -z для git-grep вместе с xargs -0 .
  • 0
    git grep очевидно, имеет смысл только в git repo. Общая замена будет grep -r .
Показать ещё 2 комментария
7

Я просто нуждался в этом и не был доволен скоростью доступных примеров. Поэтому я придумал свой собственный:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grep очень эффективен при поиске соответствующих файлов. Эта команда заменила ~ 145 000 файлов ветром, в то время как другие заняли так много времени, что я не мог дождаться их завершения.

  • 0
    grep -ril 'subdomainA' * , но grep -ril 'subdomainA' * далеко не так быстр, как grep -Hr 'subdomainA' * | cut -d: -f1 .
  • 0
    @Henno: только один вопрос: как я могу исключить двоичные файлы (исполняемые файлы) ?
Показать ещё 3 комментария
7
find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

find /home/www/ -type f отобразит все файлы в/home/www/(и его подкаталоги). Флаг "-exec" сообщает find для запуска следующей команды для каждого найденного файла.

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

- это запуск команды по файлам (по одному за раз). {} заменяется именами файлов. + в конце команды сообщает find создать одну команду для многих имен файлов.

На странице find man: "Командная строка построена так же, как и xargs строит свои командные строки.

Таким образом, вы можете достичь своей цели (и обрабатывать имена файлов, содержащие пробелы), не используя xargs -0 или -print0.

6

Чтобы сократить рекурсивные файлы sed через, вы могли бы grep для вашего экземпляра строки:

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

Если вы запустите man grep, вы заметите, что вы также можете определить флаг --exlude-dir="*.git", если вы хотите опустить поиск по каталогам .git, избегая проблем с индексами git, как другие вежливо указали.

Приведем вас к:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g
3

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

Я думаю, что большинство людей не знают, что они могут что-то передать в файл "while read" и избегают этих неприятных аргументов -print0, в то время как сохраняются пробелы в именах файлов.

Дальнейшее добавление echo до того, как sed позволит вам увидеть, какие файлы будут изменяться, прежде чем делать это.

  • 0
    Причина -print0 полезна в том, что она обрабатывает случаи, которые в while read просто не может обработать - -print0 является допустимым символом в имени файла Unix, поэтому для того, чтобы ваш код был полностью устойчивым, он должен также справляться с такими именами файлов , (Кроме того, вы хотите, чтобы read -r избегал какого-то досадного устаревшего поведения POSIX при read .)
  • 0
    Кроме того, sed не используется, если нет совпадений, поэтому grep самом деле не нужен; хотя это полезная оптимизация, позволяющая избежать перезаписи файлов, которые не содержат совпадений, если у вас их много или вы хотите без необходимости обновлять отметки даты на файлах.
3
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done
3

Попробуйте следующее:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`
  • 1
    Привет @RikHic, хороший совет - думал о чем-то вроде этого; к сожалению, вышеприведенное форматирование оказалось не совсем правильным :) Так что я постараюсь с предварительным тегом (не работает), поэтому с экранированием обратных пометок тогда: sed -i 's/subdomainA/subdomainB/g' ` grep -ril 'subdomainA' /home/www/* `- это все равно выглядит не слишком хорошо, но должно выдержать копирование пасты :) Ура!
2

Если вы не против использования vim вместе с инструментами grep или find, вы можете выполнить ответ, указанный пользователем Gert в этой ссылке → How выполнить замену текста в иерархии больших папок?.

Здесь сделка:

  • рекурсивно grep для строки, которую вы хотите заменить в определенном пути, и взять только полный путь к соответствующему файлу. (это будет $(grep 'string' 'pathname' -Rl).

  • (необязательно), если вы хотите сделать предварительную резервную копию этих файлов в централизованном каталоге, возможно, вы также можете использовать это: cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • после этого вы можете редактировать/заменять по желанию в vim по схеме, аналогичной той, которая указана в приведенной ссылке:

    • :bufdo %s#string#replacement#gc | update
1

чтобы избежать изменения.

  • NearlysubdomainA.example.com
  • subdomainA.example.comp.other

но все же

  • subdomainA.example.com.IsIt.good

(может быть, не очень хорошо в идее, лежащей в основе корня домена)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;
1

Немного старой школы, но это работало на OS X.

Есть несколько обманов:

• Будет редактировать файлы с расширением .sls только в текущем каталоге

. должен быть экранирован, чтобы гарантировать, что sed не оценивает их как "любой символ"

, используется как разделитель sed вместо обычного /

Также обратите внимание, что это редактирование шаблона Jinja для передачи variable в пути import (но это не в тему).

Сначала убедитесь, что ваша команда sed делает то, что вы хотите (это приведет только к печати изменений в stdout, это не изменит файлы):

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Отредактируйте команду sed по мере необходимости, как только вы готовы внести изменения:

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Обратите внимание на -i '' в команде sed, я не хотел создавать резервную копию исходных файлов (как описано в Редактирование на месте с sed на OS X или в комментарии Роберта Луджо на этой странице).

Счастливые люди седанов!

1

Вы можете использовать awk для решения этой проблемы, как показано ниже,

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

надеюсь, что это поможет вам!

  • 0
    Работает на MacOs без проблем! Все команды на основе sed не выполнялись, когда были включены двоичные файлы даже с особыми настройками osx.
  • 0
    Тщательный ... это взорвется , если какие - либо из файлов find возвращения имеют место в их именах! Гораздо безопаснее использовать while read : stackoverflow.com/a/9612560/1938956
1

Используя комбинацию grep и sed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done
  • 0
    mywiki.wooledge.org/DontReadLinesWithFor
  • 0
    @tripleee Я немного изменил это. В этом случае для вывода команды grep -Rl pattern генерируется список файлов, в которых находится шаблон. Файлы не читаются в for цикла.
Показать ещё 2 комментария
1

Если вы хотите использовать это без полного уничтожения своего репозитория SVN, вы можете сказать "найти", чтобы игнорировать все скрытые файлы, выполнив:

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'
  • 0
    Скобки кажутся излишними. Ранее у этого была ошибка форматирования, которая делала его непригодным для использования (рендеринг Markdown сожрал бы некоторые символы из регулярного выражения).
1

Для Qshell (qsh) для IBMi, а не bash как помечено OP.

Ограничения команд qsh:

  • find не имеет опции -print0
  • xargs не имеет опции -0
  • sed не имеет опции -i

Таким образом, решение в qsh:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

Предостережения:

  • Решение исключает обработку ошибок
  • Не bash помечается OP
  • 0
    Это имеет некоторые неприятные проблемы с цитированием и чтением строк с for .
0
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`
  • 1
    Не используется awk / sed , но распространен perl (за исключением встроенных систем / только с busybox).
0

Для замены всех вхождений в репозитории git вы можете использовать:

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

Смотрите Список файлов в локальном git repo? для других опций, чтобы перечислить все файлы в репозитории. Опция -z сообщает git отделить имена файлов с нулевым байтом, что гарантирует, что xargs (с опцией -0) может разделять имена файлов, даже если они содержат пробелы или что-то еще.

0

Если у вас есть доступ к node, вы можете сделать npm install -g rexreplace, а затем

rexreplace 'subdomainA.example.com' 'subdomainB.example.com' /home/www/**/*.*
0

Чтобы заменить все соответствие содержания string_1 на string_2 всех файлов .c и .h в текущем каталог и подкаталоги (исключая .git/).

Это работает на Mac:

find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
sed -i '' -e 's/'$1'/'$2'/g' {} +

Это должно работать на Linux (еще не протестировано):

find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
sed -i 's/string_1/string_2/g' {} +
0

Это лучшее решение, которое я нашел для OSX и Windows (msys2). Должен работать со всем, что может получить версию gnu sed. Пропускает каталоги .git, поэтому он не повредит ваши контрольные суммы.

На mac, сначала установите coreutils и убедитесь, что gsed находится в пути -

brew install coreutils

Затем я использую эту функцию в моем zshrc/bashrc →

replace-recursive() {
    hash gsed 2>/dev/null && local SED_CMD="gsed" || SED_CMD="sed"
    find . -type f -name "*.*" -not -path "*/.git/*" -print0 | xargs -0 $SED_CMD -i "s/$1/$2/g"
}

usage: replace-recursive <find> <replace>
0

изменить несколько файлов (и сохранить резервную копию как *.bak):

perl -p -i -e "s/\|/x/g" *

возьмет все файлы в каталоге и заменит "|" с х называемый "Perl pie" (простой как пирог)

  • 0
    Не рекурсивный через каталоги все же.
  • 0
    к нему можно подключиться по конвейеру, что делает его очень настраиваемым, в том числе с помощью каталогов. josephscott.org/archives/2005/08/… и unix.stackexchange.com/questions/101415/…
0

Проще всего использовать нижеследующее в командной строке

find /home/www/ -type f|xargs perl -pi -e 's/subdomainA\.example\.com/subdomainB.example.com/g' 

Ещё вопросы

Сообщество Overcoder
Наверх
Меню