Найти и заменить внутри текстового файла с помощью команды Bash

561

Какой самый простой способ найти и заменить данную входную строку, скажем abc, и заменить другой строкой, скажем, XYZв файле /tmp/file.txt?

Я пишу приложение и использую IronPython для выполнения команд через SSH - но я не очень хорошо знаю Unix и не знаю, что искать.

Я слышал, что Bash, помимо интерфейса командной строки, может быть очень мощным языком сценариев. Итак, если это правда, я предполагаю, что вы можете выполнять такие действия.

Могу ли я сделать это с помощью bash, и какой самый простой (однострочный) скрипт для достижения моей цели?

ясень
источник

Ответы:

930

Самый простой способ - использовать sed (или perl):

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

Который будет вызывать sed для редактирования на месте из-за -iопции. Это можно назвать из bash.

Если вы действительно хотите использовать только 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/..../..../ - Substituteand /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 будет доступен в системе. Но это очень редко в настоящее время. Возможно, я показываю свой возраст.
Slim
3
Можете ли вы показать более сложный пример. Что-то вроде замены "chdir / blah" на "chdir / blah2". Я пытался perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, но продолжаю получать сообщение об ошибке. Отсутствие пробела между шаблоном и следующим словом устарело в строке -e 1. Не соответствует (в регулярном выражении; помечено <- HERE в m / (chdir) () (<- HERE ?: \\ / at -e строка 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больше об этом.

rayro
источник
12
Здесь возможны две вещи: а) replaceполезный независимый инструмент, и люди MySQL должны выпускать его отдельно и зависеть от него; б) replaceтребует немного MySQL o_O. В любом случае, установка mysql-сервера для замены будет неправильным решением. :)
Филип Уайтхаус
работает только для Mac? В моем Ubuntu я Centos, что команда не существует
Пол
1
Это потому, что у вас не установлен mysql-serverпакет. Как указано @rayro, replaceявляется частью этого.
Phius
2
«Предупреждение: замена устарела и будет удалена в следующей версии».
Стивен Вачон
1
Будьте осторожны, чтобы не запускать команду REPLACE в Windows! В Windows команда REPLACE предназначена для быстрой репликации файлов. Не имеет отношения к этому обсуждению.
Maor
53

Это старый пост, но для тех, кто хочет использовать переменные, как @centurian сказал, что одинарные кавычки означают, что ничего не будет расширено.

Простой способ получить переменные - это выполнить конкатенацию строк, так как это делается с помощью сопоставления в bash: должно работать следующее:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
zcourts
источник
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 без полноэкранного режима. Вы можете дать ему те же команды, что и в приглашении 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. Этот вид синтаксического анализа должен быть медленнее, чем поиск и замена одним нажатием, но, как вы уже видели, есть четыре переменные для четырех различных шаблонов поиска, работающих в одном цикле анализа.

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

Centurian
источник
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' {} \;
MMParvin
источник
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

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

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

johnraff
источник
Любые советы по использованию этого с символами табуляции? По какой-то причине мой скрипт не находит ничего с вкладками после перехода с sed с большим количеством экранирования к этому методу.
Брайан Ханней
2
Если вы хотите поместить вкладку в заменяемую строку, вы можете сделать это с помощью синтаксиса Bash «dollared одинарные кавычки», поэтому вкладка представляется как $ '\ t', и вы можете сделать $ echo 'tab' $ '\ t''separated'> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff
5

Попробуйте следующую команду оболочки:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
источник
4
Это отличный ответ на вопрос «как мне случайно все файлы во всех подкаталогах тоже», но, похоже, это не то, что здесь спрашивают.
tripleee
Этот синтаксис не будет работать для версии BSD sed, используйте sed -i''вместо этого.
Кенорб
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!.

Смотрите также:

kenorb
источник
Хотел сделать замены в интерактивном режиме. Поэтому попытался "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Но терминал застрял сейчас. Как мы будем делать замены в интерактивном режиме, а оболочка bash не будет вести себя странно.
vineeshvs
Для отладки, добавления -V1, принудительного выхода используйте wq!.
Кенорб
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()
micalith
источник
1

Вы можете использовать команду rpl. Например, вы хотите изменить доменное имя во всем проекте php.

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

Это не совсем понятно, но это очень быстро и полезно. :)

zalex
источник