Как заменить строку в нескольких файлах в командной строке Linux

461

Мне нужно заменить строку в большом количестве файлов в папке с sshдоступом только к серверу. Как я могу это сделать?

mridul4c
источник

Ответы:

602
cd /path/to/your/folder
sed -i 's/foo/bar/g' *

Появления «foo» будут заменены на «bar».

В системах BSD, таких как macOS, вам необходимо предоставить расширение для резервной копии, например -i '.bak'или «риск повреждения или частичное содержимое» для man-страницы.

cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *
кэв
источник
13
Это не работает для меня, если в строке есть пробелы или специальные символы. Любая идея, почему это может быть, или мне нужно как-то избежать их? Спасибо!
Мэтью Хербст
13
По крайней мере, для моей версии sed, -iтребуется строка после нее, которая добавляется к старым именам файлов. Поэтому sed -i .bak 's/foo/bar/g' *.xxперемещает все .xxфайлы к эквивалентному .xx.bakимени и затем генерирует .xxфайлы с подстановкой foo → bar.
Анафора
24
Если кто-то хочет искать больше вариантов, есть ответ на обмен стека Unix, который охватывает больше вариантов использования сайта unix.stackexchange.com/a/112024/13488
Reddy
19
@MatthewHerbst, чтобы избежать пробелов, используйте \ like sed -i 's/foo\ with\ spaces/bar/g' *. Я полагаю, вы узнали об этом так долго ... но так остаётся другим, которые находят ту же проблему.
Мануэльвигарсия
5
Для чего-то рекурсивного вы можете попробовать следующее. Обратите внимание, что это не работает, если список файлов огромен. sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
шотландец
244

Аналогично ответу Каспара, но с флагом g, чтобы заменить все вхождения в строке.

find ./ -type f -exec sed -i 's/string1/string2/g' {} \;

Для глобального нечувствительного к регистру:

find ./ -type f -exec sed -i 's/string1/string2/gI' {} \;
Селин Ауссурд
источник
23
Если вы работаете в OSX и ваши шаблоны могут содержать точки и вы хотите заменить на месте (без файла резервной копии), вы должны использовать LC_ALL=C find ./ -type f -exec sed -i '' -e 's/proc.priv/priv/g' {} \;(см. Этот пост и этот )
Джонатан Х
2
Это определенно сработало для меня, так как позволяет фильтровать с -name "* .js" '
Marcello de Sales
1
Подробный вариант был бы классным, но вы можете просто повторить, чтобы увидеть, были ли внесены изменения. Примечание: Для подстановочных знаков попробуйте '-name "* .php"', и grep плохо работает с рекурсией и подстановочными знаками, вы должны добавить --include=*.whateverс -r
PJ Brunet
12
Не делайте этого из корня проверенного репозитория Git. Вы можете случайно повредить его базу данных в .git/. Обязательно cdопускайтесь на более низкий уровень.
erikprice
1
Я только что сделал это в моем мерзавца репо и теперь git statusвозвращается: error: bad index file sha1 signature.... fatal: index file corrupt. Что дает?
Джин
165

Ответ @ kev хорош, но влияет только на файлы в ближайшем каталоге. В приведенном ниже примере используется grep для рекурсивного поиска файлов. Это работает для меня каждый раз.

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

Распределение команд

Grep -r : --recursive , рекурсивно прочитать все файлы в каждом каталоге.
grep -l : --print-with-match , печатает имя каждого файла, у которого есть совпадение, вместо печати совпадающих строк.
grep -i : --ignore-case .

xargs : преобразовать STDIN в аргументы, следуйте этому ответу .
Команда xargs -i @ ~ содержит @ ~ : заполнитель для аргумента, который будет использоваться в определенной позиции в ~ команде ~ , знак @ - это заполнитель, который может быть заменен любой строкой.

sed -i : редактировать файлы на месте, без резервных копий.
СЭД с / регулярное выражение / замена / : заменой строки сопоставления регулярное выражение с заменой .
sed s / regexp / replace / g : global , делать замену для каждого совпадения вместо только первого совпадения.

pymarco
источник
13
это не сработало для меня, но это сработало:grep --include={*.php,*.html,*.js} -rnl './' -e "old-word" | xargs -i@ sed -i 's/old-word/new-word/g' @
Деннис
7
@pymarco Можете ли вы объяснить эту команду? Я не знаю, почему вам пришлось использовать xargs вместо использования sed после |, также, почему -iв команде xargs? Я прочитал в руководстве это устарело и должен использовать -Iвместо этого. И @используется как разделитель для начала и конца для шаблона?
Эдсон Орасио Джуниор
2
вопрос специально для Linux, хотя.
pymarco
6
На OSX это нормально:grep -rli 'old-word' * | xargs -I@ sed -i '' 's/2.0.0/latest/g' @
Филипп Султан
1
Было бы неплохо, если бы вы отказались от некоторых опций ( rli) и @знака, например
Роймунсон
37

Это сработало для меня:

find ./ -type f -exec sed -i 's/string1/string2/' {} \;

Но это не так sed -i 's/string1/string2/g' *. Может быть, «foo» не был строкой1, а «bar» - не строкой2.

Каспар Л. Пальги
источник
1
Это потому, что sed трактует подстановочный знак * по-разному. [abc] * означает произвольное количество символов в наборе {a, b, c}. [a-z0-9] * работает аналогично шаблону *.
thepiercingarrow
8
Об использовании OSX:find ./ -type f -exec sed -i '' -e 's/string1/string2/' {} \;
Shaheen Ghiassy
32

Есть несколько стандартных ответов на этот вопрос. Как правило, вы можете использовать find для рекурсивного вывода списка файлов, а затем выполнять операции с помощью sed или perl .

Для наиболее быстрого использования команда rpl гораздо проще запомнить. Вот замена (foo -> bar), рекурсивно для всех файлов:

rpl -R foo bar .

Вам, вероятно, понадобится установить его ( apt-get install rplили аналогичный).

Однако для более сложных заданий, которые включают регулярные выражения и обратную подстановку или переименование файлов, а также поиск и замену, наиболее общий и мощный инструмент, о котором я знаю, это repren , небольшой скрипт на Python, который я написал некоторое время назад для некоторых более сложные задачи переименования и рефакторинга. Причины, по которым вы можете предпочесть это:

  • Поддержка переименования файлов, а также поиск и замена содержимого файла.
  • Просмотрите изменения, прежде чем совершать поиск и замену.
  • Поддержка регулярных выражений с обратной подстановкой, целыми словами, без учета регистра и с сохранением регистра (замените режимы foo -> bar, Foo -> Bar, FOO -> BAR).
  • Работает с множественными заменами, включая перестановки (foo -> bar и bar -> foo) или наборы неуникальных замен (foo -> bar, f -> x).

Чтобы использовать это pip install repren. Проверьте README для примеров.

jlevy
источник
1
Вау, репрен отлично! Просто использовал его, чтобы изменить часть слова в именах классов, методах и переменных, переименовывая файлы, чтобы они соответствовали более чем 1000+ заголовочных и исходных файлов C ++, и с первого раза он отлично работал с одной командой. Спасибо!
Боб
29

Чтобы заменить строку в нескольких файлах, вы можете использовать:

grep -rl string1 somedir/ | xargs sed -i 's/string1/string2/g'

Например

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

Исходный блог

Djacomo
источник
20

Чтобы заменить путь в файлах (избегая escape-символов), вы можете использовать следующую команду:

sed -i 's@old_path@new_path@g'

Знак @ означает, что все специальные символы должны игнорироваться в следующей строке.

Илья Суздальницкий
источник
2
Именно то, что я искал во всех других ответах. А именно, как обращаться со специальными символами, например, при изменении строки, которая является путем. Спасибо. Похоже, большой упущение в других ответах.
inspirednz
19

Если в вашей строке есть косая черта (/), вы можете изменить разделитель на «+».

find . -type f -exec sed -i 's+http://example.com+https://example.com+g' {} +

Эта команда будет выполняться рекурсивно в текущем каталоге.

gopiariv
источник
Спасибо! Помогли изменить кучу ссылок ../domain.comнаdomain.com
Джек Роджерс
12

Первая строка вхождения «foo» будет заменена на «bar». И вы можете использовать вторую строку для проверки.

grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c
jiasir
источник
6
Почему вы ссылаетесь на свой блог? Он содержит точно такой же текст, как и ваш ответ.
ДэвидПостилл
1
я какgrep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
Prachi
10

Если у вас есть список файлов, которые вы можете использовать

replace "old_string" "new_string" -- file_name1 file_name2 file_name3

Если у вас есть все файлы, которые вы можете использовать

replace "old_string" "new_string" -- *

Если у вас есть список файлов с расширением, вы можете использовать

replace "old_string" "new_string" -- *.extension
Павел Дубиель
источник
2
на самом деле, "--file" должен быть просто "-", по крайней мере, в моей версии
simpleuser
4
Эта утилита распространяется в пакетах MySQL.
Дмитрий Гинзбург
2
Хотя я хотел бы по достоинству оценить решение, которое работает без использования кавычек в качестве регулярного выражения.
Дмитрий Гинзбург
1
Это работает нормально - и если вы хотите заменить все строки в нескольких файлах, которые заканчиваются, например, на «.txt», вы можете просто сделатьreplace "old_string" "new_string" -- *.txt
tsveti_iko
1
Лучше добавить откуда взять эту replaceутилиту. Без этой информации этот ответ неполон.
Sitesh
8

«Вы также можете использовать find и sed, но я обнаружил, что эта маленькая строчка perl прекрасно работает.

perl -pi -w -e 's/search/replace/g;' *.php
  • -e означает выполнить следующую строку кода.
  • -i означает редактировать на месте
  • -пишем предупреждения
  • петля

"(Извлечено из http://www.liamdelahunty.com/tips/linux_search_and_replace_multiple_files.php )

Мои лучшие результаты получены от использования Perl и grep (чтобы убедиться, что файл имеет выражение поиска)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
Алехандро Саламанка Мазуэло
источник
8

Если вы хотите найти строку searchи заменить ее replaceв нескольких файлах, это моя проверенная в бою формула в одну строку :

grep -RiIl 'search' | xargs sed -i 's/search/replace/g'

Быстрое объяснение grep:

  • -R - рекурсивный поиск
  • -i - без учета регистра
  • -I пропустить двоичные файлы (вы хотите текст, верно?)
  • -l- распечатать простой список в качестве вывода. Необходим для других команд

Затем вывод grep передается в sed (через xargs), который фактически используется для замены текста. -iФлаг будет изменять файл напрямую. Снимите его для своего рода «пробного запуска».

невежественный
источник
4

Я придумал собственное решение, прежде чем нашел этот вопрос (и ответы). Я искал различные комбинации «заменить», «несколько» и «xml», потому что это было мое приложение, но не нашел именно этого.

Моя проблема: у меня были весенние xml-файлы с данными для тестовых случаев, содержащие сложные объекты. Рефакторинг исходного кода java изменил много классов и не применим к файлам данных xml. Чтобы сохранить данные тестовых случаев, мне нужно было изменить все имена классов во всех XML-файлах, распределенных по нескольким каталогам. Все это при сохранении резервных копий оригинальных XML-файлов (хотя это не было обязательным, так как контроль версий спас бы меня здесь).

Я искал некоторую комбинацию find+ sed, потому что она работала для меня в других ситуациях, но не с несколькими заменами одновременно.

Затем я нашел ответ на запрос ubuntu, и он помог мне построить мою командную строку:

find -name "*.xml" -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

И это сработало отлично (ну, в моем случае было шесть разных замен). Но учтите, что он затронет все файлы * .xml в текущем каталоге. Из-за этого, и если вы подотчетны системе контроля версий, вы можете сначала фильтровать и передавать только sedтем, у кого действительно есть нужные вам строки; подобно:

find -name "*.xml" -exec grep -e "firstWord" -e "secondWord" -e "thirdWord" {} \; -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;
manuelvigarcia
источник
а для windows я только что узнал, что есть способ найти строку - пока не проверял, как ее заменить - одной командой: findstr /spin /c:"quéquieresbuscar" *.xml это будет удобно.
Мануэльвигарсия
3
grep --include={*.php,*.html} -rnl './' -e "old" | xargs -i@ sed -i 's/old/new/g' @
Деннис
источник
3

Действительно хромая, но я не мог заставить любую из команд sed работать правильно на OSX, поэтому вместо этого я сделал эту глупую вещь:

:%s/foo/bar/g
:wn

^ - скопируйте эти три строки в мой буфер обмена (да, включите завершающий перевод строки), затем:

VI *

и удерживайте нажатой команду -v, пока не появится сообщение, что файлов больше не осталось.

Тупой ... Hacky ... эффективный ...

Джастин
источник
3

На MacBook Pro я использовал следующее (вдохновлено https://stackoverflow.com/a/19457213/6169225 ):

sed -i '' -e 's/<STR_TO_REPLACE>/<REPLACEMENT_STR>/g' *

-i '' убедитесь, что вы не делаете никаких резервных копий.

-e для современного регулярного выражения.

Marquistador
источник
3
-eпросто говорит sed, что следующий токен - это команда. Для расширенных регулярных выражений используйте -Eвместо этого.
Бенджамин В.
2

Редактор потока изменяет несколько файлов «на месте» при вызове с -iкоммутатором, который принимает в качестве аргумента файл резервной копии, заканчивающийся аргументом. Так

sed -i.bak 's/foo/bar/g' *

заменяет fooна barво всех файлах в этой папке, но не спускается в подпапки. Это, однако, сгенерирует новый .bakфайл для каждого файла в вашем каталоге. Чтобы сделать это рекурсивно для всех файлов в этом каталоге и всех его подкаталогах, вам нужен помощник, например find, для обхода дерева каталогов.

find ./ -print0 | xargs -0 sed -i.bak 's/foo/bar/g' *

findпозволяет вам дополнительно ограничивать, какие файлы изменять, указав дополнительные аргументы, например find ./ -name '*.php' -or -name '*.html' -print0, при необходимости.


Примечание: GNU sedне требует окончания файла, также sed -i 's/foo/bar/g' *будет работать; FreeBSD sedтребует расширения, но допускает пробел между ними, так что sed -i .bak s/foo/bar/g *работает.

Anaphory
источник
2

скрипт для мультиредактной команды

multiedit [-n PATTERN] OLDSTRING NEWSTRING

Из ответа Каспара я сделал скрипт bash, чтобы принимать аргументы командной строки и, необязательно, ограничивать имена файлов, соответствующие шаблону. Сохраните в вашем $ PATH и сделайте исполняемый файл, затем просто используйте команду выше.

Вот сценарий:

#!/bin/bash
_help="\n
Replace OLDSTRING with NEWSTRING recursively starting from current directory\n
multiedit [-n PATTERN] OLDSTRING NEWSTRING\n

[-n PATTERN] option limits to filenames matching PATTERN\n
Note: backslash escape special characters\n
Note: enclose STRINGS with spaces in double quotes\n
Example to limit the edit to python files:\n
multiedit -n \*.py \"OLD STRING\" NEWSTRING\n"

# ensure correct number of arguments, otherwise display help...
if [ $# -lt 2 ] || [ $# -gt 4 ]; then echo -e $_help ; exit ; fi
if [ $1 == "-n" ]; then  # if -n option is given:
        # replace OLDSTRING with NEWSTRING recursively in files matching PATTERN
        find ./ -type f -name "$2" -exec sed -i "s/$3/$4/g" {} \;
else
        # replace OLDSTRING with NEWSTRING recursively in all files
        find ./ -type f -exec sed -i "s/$1/$2/" {} \;
fi
BBW перед Windows
источник
1

Если файл содержит обратную косую черту (обычно пути), вы можете попробовать что-то вроде этого:

sed -i -- 's,<path1>,<path2>,g' *

например:

sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)
Шайк Аманулла
источник
1

Чтобы сохранить свой личный английский узел, я написал служебный скрипт, который помогает рекурсивно заменить несколько пар старой / новой строки для всех файлов в каталоге.

Несколько пар старой / новой строки управляются в хэш-карте.

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

Требуется Bash 4.2, из-за некоторых новых функций.

en_standardize.sh:

#! /bin/bash
# (need bash 4.2+,)
# 
# Standardize phonetic symbol of English.
# 
# format:
#   en_standardize.sh [<dir>]
# 
# params:
# * dir
#   target dir, optional,
#   if not specified then use environment variable "$node_dir_en",
#   if both not provided, then will not execute,
# * 
# 

paramCount=$#

# figure target dir,
if [ $paramCount -ge 1 ]; then # dir specified
    echo -e "dir specified (in command):\n\t$1\n"
    targetDir=$1
elif [[ -v node_dir_en ]]; then # environable set,
    echo -e "dir specified (in environment vairable):\n\t$node_dir_en\n"
    targetDir=$node_dir_en
else # environable not set,
    echo "dir not specified, won't execute"
    exit
fi

# check whether dir exists,
if [ -d $targetDir ]; then
    cd $targetDir
else
    echo -e "invalid dir location:\n\t$targetDir\n"
    exit
fi

# initial map,
declare -A itemMap
itemMap=( ["ɪ"]="i" ["ː"]=":" ["ɜ"]="ə" ["ɒ"]="ɔ" ["ʊ"]="u" ["ɛ"]="e")

# print item maps,
echo 'maps:'
for key in "${!itemMap[@]}"; do
    echo -e "\t$key\t->\t${itemMap[$key]}"
done
echo -e '\n'

# do replace,
for key in "${!itemMap[@]}"; do
    grep -rli "$key" * | xargs -i@ sed -i "s/$key/${itemMap[$key]}/g" @
done

echo -e "\nDone."
exit
Эрик Ван
источник
1

Использование команды ack будет намного быстрее:

ack '25 Essex' -l | xargs sed -i 's/The\ fox \jump/abc 321/g'

Также, если у вас есть пробел в результатах поиска. Вы должны избежать этого.

Патоши パ ト シ
источник
0

Я привожу пример для исправления распространенной ошибки shebang в источниках Python.

Вы можете попробовать подход grep / sed. Вот тот, который работает с GNU sed и не сломает git-репо:

$ grep -rli --exclude '*.git*' '#!/usr/bin/python' . | xargs -I {} \
gsed -i '' -e 's/#!\/usr\/bin\/python/#!\/usr\/bin\/env python/' {}

Или вы можете использовать greptile :)

$ greptile -x .py -l -i -g '#!/usr/bin/env python' -r '#!/usr/bin/python' .

Я только что проверил первый скрипт, и второй тоже должен работать. Будьте осторожны с escape-символами, я думаю, что в большинстве случаев проще использовать greptile. Конечно, вы можете делать много интересных вещей с помощью sed, и для этого предпочтительнее освоить его с помощью xargs.

экса
источник
0

Я нашел это из другого поста (не помню, какой именно), и хотя он не самый элегантный, он прост и, как начинающий пользователь Linux, не доставил мне никаких проблем.

for i in *old_str* ; do mv -v "$i" "${i/\old_str/new_str}" ; done

если у вас есть пробелы или другие специальные символы, используйте \

for i in *old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

для строк в подкаталогах используйте **

for i in *\*old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done
Кайл Терк
источник
0

Команда ниже может быть использована для поиска файлов и замены файлов:

find . | xargs grep 'search string' | sed 's/search string/new string/g'

Например

find . | xargs grep abc | sed 's/abc/xyz/g'
Amit
источник
0

Существует более простой способ с использованием простого файла сценария:

   # sudo chmod +x /bin/replace_string_files_present_dir

откройте файл в gedit или редакторе по вашему выбору, я использую gedit здесь.

   # sudo gedit /bin/replace_string_files_present_dir

Затем в редакторе вставьте следующее в файл

   #!/bin/bash
   replace "oldstring" "newstring" -- *
   replace "oldstring1" "newstring2" -- *
   #add as many lines of replace as there are your strings to be replaced for 
   #example here i have two sets of strings to replace which are oldstring and 
   #oldstring1 so I use two replace lines.

Сохраните файл, закройте gedit , затем выйдите из терминала или просто закройте его и затем запустите, чтобы иметь возможность загрузить новый скрипт, который вы добавили.

Перейдите в каталог, где у вас есть несколько файлов, которые вы хотите редактировать. Затем запустите:

  #replace_string_files_present_dir

Нажмите ввод, и это автоматически заменит oldstring и oldstring1 во всех файлах, которые их содержат, на правильные newstring и newstring1 соответственно.

Он пропустит все каталоги и файлы, которые не содержат старых строк.

Это может помочь в устранении утомительной работы при наборе текста, если у вас есть несколько каталогов с файлами, которые требуют замены строки. Все, что вам нужно сделать, это перейти к каждому из этих каталогов, а затем выполнить:

#replace_string_files_present_dir

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

replace "oldstring" "newstring" -- *

в конце файла / bin / replace_string_files_present_dir .

Чтобы добавить новую строку замены, просто откройте созданный нами скрипт, набрав в терминале следующее:

sudo gedit /bin/replace_string_files_present_dir

Не беспокойтесь о количестве строк замены, которые вы добавляете, они не будут действовать, если старая строка не найдена.

briancollins081
источник
Обычно, когда люди спрашивают «как получить $ {что-нибудь}» в bash, они просят, чтобы компактная версия была включена в инструкцию сценария или задания CI (скажем, a .gitlab-ci.ymlили a travis.yml). Каждый поворот, заключающийся в написании сценария для его последующего выполнения, является анти-паттерном, поскольку вам нужно будет создать сценарий для создания сценария (а я в большинстве случаев
путаюсь
-4

$ replace "From" "To" - `find / path / to / folder -type f`

(replace - утилита замены строк MySQL Database System)

bashconsole
источник