Знайдіть і замініть всередині текстового файлу команду Bash


561

Який найпростіший спосіб знайти і замінити заданий рядок введення, скажімо abc, і замінити іншим рядком, скажімо, XYZу файлі /tmp/file.txt?

Я пишу додаток і використовую IronPython для виконання команд через SSH - але я не знаю Unix, що добре, і не знаю, що шукати.

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

Чи можу я це зробити з bash, і який найпростіший (один рядок) сценарій для досягнення своєї мети?

Відповіді:


928

Найпростіший спосіб - використовувати sed (або perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Котрий викликатиме sed для того, щоб зробити зміни на місці через -iопцію. Це можна назвати від баш.

Якщо ви дійсно хочете використовувати просто bash, то можливе наступне:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

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


3
За винятком того, що виклик mv є майже таким же, як "non Bash", як використання sed. Я майже сказав те саме, що відлуння, але це вбудована оболонка.
стрункий

5
Аргумент -i для sed не існує для Solaris (і я думаю, що деякі інші реалізації), однак, майте це на увазі. Просто витратив кілька хвилин на те, щоб зрозуміти це ...
Панкі

2
Зауважте до себе: про регулярне вираження sed: s/..../..../ - Substituteі /g - Global
контрольна сума

89
Примітка для користувачів Mac, які отримують invalid command code Cпомилку ... Для заміни на місці BSD sedвимагає розширення файлу після -iпрапора, оскільки він зберігає файл резервної копії із заданим розширенням. Наприклад: sed -i '.bak' 's/find/replace/' /file.txt Ви можете пропустити резервну копію, використовуючи порожній рядок так:sed -i '' 's/find/replace/' /file.txt
Остін,

8
Порада: Якщо ви хочете використовувати невідчутливий для репальсів випадокs/abc/XYZ/gi
Борис Д. Теохаров

166

Маніпуляції файлами зазвичай не проводяться Bash, але програмами, на які посилається Bash, наприклад:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

-iПрапор вказує йому зробити заміну на місці.

Див. man perlrunДокладнішу інформацію, зокрема про те, як зробити резервну копію вихідного файлу.


37
Пурист в мені каже, що ви не можете бути впевнені, що Perl буде доступний у системі. Але це сьогодні дуже рідко. Можливо, я показую свій вік.
струнка

3
Чи можете ви показати більш складний приклад. Щось на зразок заміни "chdir / blah" на "chdir / blah2". Я спробував perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, але я продовжую отримувати помилку, коли немає пробілу між шаблоном та наступним словом, застаріле в рядку -e 1. Незрівняно (у регулярному виразі; позначено <- ТУТ у m / (chdir) () (<- ТУТ ?: \\ / в -е рядку 1.
CMCDragonkai

@CMCDragonkai Перевір цю відповідь: stackoverflow.com/a/12061491/2730528
Альфонсо Сантьяго

69

Я здивувався, коли натрапив на це ...

Існує replaceкоманда, яка постачається з "mysql-server"пакетом, тож якщо ви її встановили, спробуйте:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Детальніше man replaceпро це див.


12
Тут можливі дві речі: а) replaceє корисним незалежним інструментом, і MySQL люди повинні випустити його окремо і залежати від нього b) replaceвимагає трохи біт MySQL o_O У будь-якому випадку, встановлення mysql-сервера для заміни було б неправильним. :)
Філіп Уайтхаус

працює лише для Mac? в моєму Убунту я CentOS , що команда не існує
Пол

1
Це тому, що у вас не встановлено mysql-serverпакет. Як вказує @rayro, replaceє його частиною.
Фіус

2
"Попередження: заміна застаріла і буде видалена в наступній версії."
Стівен Вачон

1
Будьте обережні, щоб не запустити команду REPLACE у Windows! У Windows команда REPLACE призначена для швидкої реплікації файлів. Не стосується цієї дискусії.
Маор

52

Це стара публікація, але для тих, хто хоче використовувати змінні, як @centurian сказав, що єдині лапки означають, що нічого не буде розширено.

Простий спосіб отримати змінні в - це зробити конкатенацію рядків, оскільки це робиться шляхом складання в башті, слід працювати наступним чином:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt

39

Bash, як і інші оболонки, просто інструмент для координації інших команд. Як правило, ви б намагалися використовувати стандартні команди UNIX, але, звичайно, ви можете використовувати Bash, щоб викликати будь-що, включаючи власні компільовані програми, інші скрипти оболонки, сценарії Python та Perl тощо.

У цьому випадку існує кілька способів зробити це.

Якщо ви хочете прочитати файл і записати його в інший файл, виконуючи пошук / заміну під час переходу, використовуйте sed:

sed 's/abc/XYZ/g' <infile >outfile

Якщо ви хочете відредагувати файл на місці (як якщо б відкрити файл у редакторі, відредагувати його, а потім зберегти), надайте інструкції до редактора рядків "ex"

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex - це як vi без режиму повноекранного режиму. Ви можете дати йому ті самі команди, що і у вікні ':'.


33

Я знайшов цю тему серед інших, і я згоден, що вона містить найповніші відповіді, тому я додаю і свої:

  1. sedі edтакі корисні ... від руки. Подивіться на цей код від @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Коли моє обмеження полягає в тому, щоб використовувати його в скрипті оболонки, замість "abc" або "XYZ" всередину не можна використовувати змінну. BashFAQ , здається, згодні з тим, що я розумію , по крайней мере. Отже, я не можу використовувати:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt
    

    але що ми можемо зробити? Як, @Johnny сказав, використовуйте, while read...але, на жаль, це ще не кінець історії. Наступне добре працювало зі мною:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
    
  3. Як видно, якщо nullglobвстановлено, то він поводиться дивно, коли є рядок, що містить *як у:

    <VirtualHost *:80>
     ServerName www.example.com
    

    що стає

    <VirtualHost ServerName www.example.com

    немає кінцевого кутового кронштейна, і Apache2 навіть не може завантажити.

  4. Цей вид розбору повинен проходити повільніше, ніж пошук і заміна одним ударом, але, як ви вже бачили, є чотири змінні для чотирьох різних шаблонів пошуку, що працюють за один цикл розбору.

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


12
У вашому (2) - ви можете зробити sed -e "s/$x/$y/", і це спрацює. Не подвійні цитати. Це може стати серйозно заплутаним, якщо рядки самих змінних містять символи з особливим значенням. Наприклад, якщо x = "/" або x = "\". Коли ви стикаєтесь з цими проблемами, це, ймовірно, означає, що ви повинні припинити спроби використовувати оболонку для цієї роботи.
стрункий

Привіт тонкий, я бачу, що ви також проти використання Perl. Яке ваше рішення? Тому що я насправді хочу динамічно змінити шлях у файлі, це означає, що у мене є багато / у рядку!
Махді

20

Ви можете використовувати sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Використовуйте -iдля «ігнорувати регістр» , якщо ви не впевнені , що текст , щоб знайти це abcабо ABCабо AbCі т.д.

Ви можете використовувати findі sedякщо ви не знаєте , ваше ім'я файлу:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Знайдіть та замініть у всіх файлах Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;

12

Ви також можете скористатися edкомандою для пошуку у файлі та заміни:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Детальніше див. У розділі " Редагування файлів за допомогою скриптів за допомогоюed ".


3
Це рішення не залежить від несумісності GNU / FreeBSD (Mac OSX) (на відміну від sed -i <pattern> <filename>). Дуже хороший!
Петерино

11

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

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

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

XYZ може бути змінною, наприклад $replacement, і однією з переваг використання тут sed є те, що вам не потрібно турбуватися про те, що рядок пошуку або заміни може містити символ обмежувача шаблону sed (зазвичай, але не обов'язково, /). Недоліком є ​​неможливість використання регулярних виразів або будь-якої із складніших операцій sed.


Якісь поради щодо використання цього тексту з символами вкладки? Чомусь мій сценарій не знаходить нічого із вкладками після переходу з sed з великою кількістю вхідних даних до цього методу.
Брайан Ханней

2
Якщо ви хочете помістити вкладку в рядок, який ви замінюєте, ви можете зробити це за допомогою синтаксису "Доларовані одиночні лапки" Баша, тому вкладка представлена ​​$ '\ t', і ви можете зробити $ echo 'tab' $ '\ t''separated'> тестовий файл; $ file_contents = $ (<тестовий файл); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABрозділений `
johnraff


5

Спробуйте виконати таку команду оболонки:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'

4
Це відмінна відповідь на те, "як я випадково теж усі файли у всіх підкаталогах", але це, здається, не те, що тут задають.
tripleee

Цей синтаксис не працює для версії BSD sed, використовуйте sed -i''натомість.
kenorb

5

Щоб редагувати текст у файлі неінтерактивно, вам потрібен текстовий редактор, наприклад vim.

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

vim -esnc '%s/foo/bar/g|:wq' file.txt

Це еквівалентно @slim відповідь на екс редактора , який є в основному те ж саме.

Ось кілька exпрактичних прикладів.

Заміна тексту fooз barв файлі:

ex -s +%s/foo/bar/ge -cwq file.txt

Видалення білих просторів для кількох файлів:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Усунення несправностей (коли термінал застряг):

  • Додайте -V1параметр, щоб відобразити багатослівні повідомлення.
  • Сила виходу з: -cwq!.

Дивись також:


Хотіли робити заміни інтерактивно. Отже, спробував "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Але термінал застряг зараз. Як нам робити заміни в інтерактивному режимі, без того, як шкаралупа баша поводиться дивно.
vinoeshvs

Налагоджувати, додавати -V1, змушувати вийти, використовувати wq!.
kenorb

2

Ви можете використовувати python і в скрипті bash. Я не мав особливого успіху з деякими найпопулярнішими відповідями тут, і виявив, що це працює без необхідності циклів:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()

1

Ви можете використовувати команду rpl. Наприклад, ви хочете змінити доменне ім’я в цілому PHP-проекті.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Це не зовсім зрозуміло, але це дуже швидко і корисно. :)

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