Як замінити рядок у кількох файлах у командному рядку Linux


460

Мені потрібно замінити рядок у великій кількості файлів у папці, маючи лише sshдоступ до сервера. Як я можу це зробити?

Відповіді:


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

Виникнення "foo" буде замінено на "bar".

У таких системах BSD, як macOS, вам потрібно надати розширення для резервного копіювання, як-то -i '.bak'або "ризик пошкодження або часткового вмісту" на сторінці сторінки.

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' *. Я гадаю, ви дізналися через стільки часу ... але таким чином залишається за іншими, що знаходять ту саму проблему.
manuelvigarcia

5
Для чогось рекурсивного ви можете спробувати наступне. Зауважте, що він не працює, якщо список файлів величезний. sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
Скотт

243

Схожий на відповідь Каспара, але з прапором 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' {} \;(див. Цю публікацію та цю )
Jonathan H

2
Це безумовно спрацювало для мене, оскільки воно дозволяє фільтрувати за допомогою -name "* .js" '
Marcello de Sales

1
Дослідний варіант був би класним, але ви можете просто переглянути, чи були внесені зміни. Примітка. Для підключень символів спробуйте "-name" * .php "', а grep поганий з рекурсією та підстановкою, вам потрібно додати за --include=*.whateverдопомогою -r
PJ Brunet

12
Не робіть цього з кореня перевіреного Git repo. Ви можете випадково пошкодити його базу даних .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 : --рекурсивне , рекурсивно читати всі файли в кожному каталозі.
grep -l : --print-with- match, друкує ім'я кожного файлу, який має відповідність, замість того, щоб друкувати відповідні рядки.
grep -i : --ignore-case .

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

sed -i : редагування файлів на місці, без резервного копіювання.
sed s / regexp / substitution / : замінити рядок, що відповідає regexp з заміною .
sed s / regexp / заміна / g : глобальна , зробіть заміну для кожного матчу замість лише першого.


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) та @знак, наприклад
Roymunson

37

Це працювало для мене:

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

Howerver, це не зробив: sed -i 's/string1/string2/g' *. Можливо, "foo" не мав на увазі бути string1, а "bar" - не string2.


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 або подібне).

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

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

Щоб використовувати його pip install repren,. Перевірте README на приклади.


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'

Джерело блогу


20

Щоб замінити шлях у файлах (уникаючи втечі символів), ви можете скористатися такою командою:

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

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


2
Саме те, що я шукав у всіх інших відповідях. А саме, як поводитися зі спеціальними символами, наприклад, при зміні рядка, який є контуром. Дякую. В інших відповідях це було великим недоглядом.
надихнув

19

У випадку, якщо у вашій рядку є похила коса стрілка (/), ви можете змінити роздільник на "+".

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

Ця команда буде виконуватися рекурсивно в поточному каталозі.


Дякую! Допоміг змінити набір посилань ../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

6
Чому ви посилаєтесь на свій блог? Він містить точно той самий текст, що і ваша відповідь.
DavidPostill

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 означає редагувати на місці
  • -w написати попередження
  • -p петля

"(Витягнуто з 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'

Швидке пояснення пояснення:

  • -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' {} \;

а для Windows я щойно з’ясував, що є спосіб знайти рядок - ще не перевірив, як її замінити - в одній команді: findstr /spin /c:"quéquieresbuscar" *.xml Це буде зручно.
manuelvigarcia


3

Насправді кульгавий, але я не зміг змусити жодної команди sed працювати правильно на OSX, тому я зробив цю тупу штуку замість цього:

:%s/foo/bar/g
:wn

^ - скопіюйте ці три рядки в мій буфер обміну (так, включіть закінчуючий новий рядок), потім:

vi *

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

Тупий ... хакі ... ефективний ...


3

У MacBook Pro я використав наступне (надихнув https://stackoverflow.com/a/19457213/6169225 ):

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

-i '' гарантує, що ви не робите резервних копій.

-e для сучасної виразки.


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

2

Редактор потоку змінює декілька файлів «inplace», коли викликається -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 *працює.


2

скрипт для команди multiedit

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

1

Якщо файл містить зворотні риски (зазвичай, контури), ви можете спробувати щось подібне:

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

колишній:

sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)

1

Щоб підтримувати свій особистий англійський вузол, я написав скрипт утиліти, який допомагає замінити кілька пар старого / нового рядка для всіх файлів у каталозі рекурсивно.

Кількома парами старий / новий рядок управляється в хеш-карті.

Режим можна встановити за допомогою командної лінії чи змінної середовища, карта жорстко закодована в сценарії, але ви можете змінити код для завантаження з файлу, якщо це необхідно.

Це вимагає 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' .

Я лише перевірив перший сценарій, а другий також повинен працювати. Будьте обережні з персонажами втечі, я думаю, що в більшості випадків використовувати грептил повинно бути простіше. Звичайно, ви можете зробити багато цікавих речей з 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'

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

Натисніть клавішу enter, і це автоматично замінить oldstring та oldstring1 у всіх файлах, що містять їх, на правильні newstring та newstring1 відповідно.

Він буде пропускати всі каталоги та файли, які не містять старих рядків.

Це може допомогти усунути нудну роботу введення тексту, якщо у вас є кілька каталогів з файлами, які потребують заміни рядка. Все, що вам потрібно зробити - це перейти до кожного з цих каталогів, а потім запустити:

#replace_string_files_present_dir

Все, що вам потрібно зробити, це переконатися, що ви включили або додали всі рядки заміни, як я вам показав вище:

replace "oldstring" "newstring" -- *

в кінці файлу / bin / substitu_string_files_present_dir .

Щоб додати новий рядок заміни, просто відкрийте створений нами сценарій, ввівши в терміналі наступне:

sudo gedit /bin/replace_string_files_present_dir

Не турбуйтеся про кількість доданих рядків заміни, вони не матимуть ефекту, якщо не буде знайдено oldstring .


Зазвичай, коли люди запитують "як зробити $ {what}" в bash, вони просять компактну версію, щоб включити її в інструкцію зі скрипту або завдання CI (скажімо, a .gitlab-ci.ymlчи a travis.yml). Кожен поворот, що полягає у написанні сценарію, щоб виконати його пізніше, є анти-шаблоном, тому що вам тоді потрібно буде створити сценарій створення сценарію (і я безладдя, більшість часу)
zar3bski

-4

$ замінити "Від" "До" - `знайти / шлях / до / папки -типу f`

(заміна - утиліта для заміни рядків системи баз даних MySQL)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.