[https://habr.com/ru/company/ruvds/blog/327530/](https://habr.com/ru/company/ruvds/blog/327530/) ![[45bbc220d2ddd07565210d548912aca8.jpg]] > Bash-скрипты: начало > > [Bash-скрипты, часть 2: циклы](https://habrahabr.ru/company/ruvds/blog/325928/) > > [Bash-скрипты, часть 3: параметры и ключи командной строки](https://habrahabr.ru/company/ruvds/blog/326328/) > > [Bash-скрипты, часть 4: ввод и вывод](https://habrahabr.ru/company/ruvds/blog/326594/) > > [Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями](https://habrahabr.ru/company/ruvds/blog/326826/) > > [Bash-скрипты, часть 6: функции и разработка библиотек](https://habrahabr.ru/company/ruvds/blog/327248/) > > [Bash-скрипты, часть 7: sed и обработка текстов](https://habrahabr.ru/company/ruvds/blog/327530/) > > [Bash-скрипты, часть 8: язык обработки данных awk](https://habrahabr.ru/company/ruvds/blog/327754/) > > [Bash-скрипты, часть 9: регулярные выражения](https://habrahabr.ru/company/ruvds/blog/327896/) > > [Bash-скрипты, часть 10: практические примеры](https://habrahabr.ru/company/ruvds/blog/328346/) > > [Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит](https://habrahabr.ru/company/ruvds/blog/328436/) В прошлый раз мы говорили о функциях в bash-скриптах, в частности, о том, как вызывать их из командной строки. Наша сегодняшняя тема — весьма полезный инструмент для обработки строковых данных — утилита Linux, которая называется sed. Её часто используют для работы с текстами, имеющими вид лог-файлов, конфигурационных и других файлов. ![[803892bfe548499aa763df324d40fd01.png]] Если вы, в bash-скриптах, каким-то образом обрабатываете данные, вам не помешает знакомство с инструментами [sed](https://en.wikipedia.org/wiki/Sed) и gawk. Тут мы сосредоточимся на sed и на работе с текстами, так как это — очень важный шаг в нашем путешествии по бескрайним просторам разработки bash-скриптов. ![[1ba550d25e8846ce8805de564da6aa63.png]] Сейчас мы разберём основы работы с sed, а так же рассмотрим более трёх десятков примеров использования этого инструмента. ## Основы работы с sed Утилиту sed называют потоковым текстовым редактором. В интерактивных текстовых редакторах, наподобие nano, с текстами работают, используя клавиатуру, редактируя файлы, добавляя, удаляя или изменяя тексты. Sed позволяет редактировать потоки данных, основываясь на заданных разработчиком наборах правил. Вот как выглядит схема вызова этой команды: ``` $ sed options file ``` По умолчанию sed применяет указанные при вызове правила, выраженные в виде набора команд, к `STDIN`. Это позволяет передавать данные непосредственно sed. Например, так: ``` $ echo "This is a test" | sed 's/test/another test/' ``` Вот что получится при выполнении этой команды. ![[0034002d055addd8a4856665f77b4339.png]] _Простой пример вызова sed_ В данном случае sed заменяет слово «test» в строке, переданной для обработки, словами «another test». Для оформления правила обработки текста, заключённого в кавычки, используются прямые слэши. В нашем случае применена команда вида `s/pattern1/pattern2/`. Буква «s» — это сокращение слова «substitute», то есть — перед нами команда замены. Sed, выполняя эту команду, просмотрит переданный текст и заменит найденные в нём фрагменты (о том — какие именно, поговорим ниже), соответствующие `pattern1`, на `pattern2`. Выше приведён примитивный пример использования sed, нужный для того, чтобы ввести вас в курс дела. На самом деле, sed можно применять в гораздо более сложных сценариях обработки текстов, например — для работы с файлами. Ниже показан файл, в котором содержится фрагмент текста, и результаты его обработки такой командой: ``` $ sed 's/test/another test' ./myfile ``` ![[f182713713c516cf7dd12b56d8579c98.png]] _Текстовый файл и результаты его обработки_ Здесь применён тот же подход, который мы использовали выше, но теперь sed обрабатывает текст, хранящийся в файле. При этом, если файл достаточно велик, можно заметить, что sed обрабатывает данные порциями и выводит то, что обработано, на экран, не дожидаясь обработки всего файла. Sed не меняет данные в обрабатываемом файле. Редактор читает файл, обрабатывает прочитанное, и отправляет то, что получилось, в `STDOUT`. Для того, чтобы убедиться в том, что исходный файл не изменился, достаточно, после того, как он был передан sed, открыть его. При необходимости вывод sed можно перенаправить в файл, возможно — перезаписать старый файл. Если вы знакомы с одним из предыдущих [материалов](https://habrahabr.ru/company/ruvds/blog/326594/) этой серии, где речь идёт о перенаправлении потоков ввода и вывода, вы вполне сможете это сделать. ## Выполнение наборов команд при вызове sed Для выполнения нескольких действий с данными, используйте ключ `-e` при вызове sed. Например, вот как организовать замену двух фрагментов текста: ``` $ sed -e 's/This/That/; s/test/another test/' ./myfile ``` ![[abdbac90d073f056788d64f50983f8a6.png]] _Использование ключа -e при вызове sed_ К каждой строке текста из файла применяются обе команды. Их нужно разделить точкой с запятой, при этом между окончанием команды и точкой с запятой не должно быть пробела. Для ввода нескольких шаблонов обработки текста при вызове sed, можно, после ввода первой одиночной кавычки, нажать Enter, после чего вводить каждое правило с новой строки, не забыв о закрывающей кавычке: ``` $ sed -e ' > s/This/That/ > s/test/another test/' ./myfile ``` Вот что получится после того, как команда, представленная в таком виде, будет выполнена. ![[3c8820a20ba2110e8564fd0c842c69a1.png]] _Другой способ работы с sed_ ## Чтение команд из файла Если имеется множество команд sed, с помощью которых надо обработать текст, обычно удобнее всего предварительно записать их в файл. Для того, чтобы указать sed файл, содержащий команды, используют ключ `-f`: Вот содержимое файла `mycommands`: ``` s/This/That/ s/test/another test/ ``` Вызовем sed, передав редактору файл с командами и файл для обработки: ``` $ sed -f mycommands myfile ``` Результат при вызове такой команды аналогичен тому, который получался в предыдущих примерах. ![[798725466a87616ce1a839d839b2ee4f.png]] _Использование файла с командами при вызове sed_ ## Флаги команды замены Внимательно посмотрите на следующий пример. ``` $ sed 's/test/another test/' myfile ``` Вот что содержится в файле, и что будет получено после его обработки sed. ![[2b223bbcf88ab80c8e9c41eec40f941b.png]] _Исходный файл и результаты его обработки_ Команда замены нормально обрабатывает файл, состоящий из нескольких строк, но заменяются только первые вхождения искомого фрагмента текста в каждой строке. Для того, чтобы заменить все вхождения шаблона, нужно использовать соответствующий флаг. Схема записи команды замены при использовании флагов выглядит так: ``` s/pattern/replacement/flags ``` Выполнение этой команды можно модифицировать несколькими способами. - При передаче номера учитывается порядковый номер вхождения шаблона в строку, заменено будет именно это вхождение. - Флаг `g` указывает на то, что нужно обработать все вхождения шаблона, имеющиеся в строке. - Флаг `p` указывает на то, что нужно вывести содержимое исходной строки. - Флаг вида `w file` указывает команде на то, что нужно записать результаты обработки текста в файл. Рассмотрим использование первого варианта команды замены, с указанием позиции заменяемого вхождения искомого фрагмента: ``` $ sed 's/test/another test/2' myfile ``` ![[a54fe87f0fc9b8d27b06f63567f226c6.png]] Вызов команды замены с указанием позиции заменяемого фрагмента Тут мы указали, в качестве флага замены, число 2. Это привело к тому, что было заменено лишь второе вхождение искомого шаблона в каждой строке. Теперь опробуем флаг глобальной замены — `g`: ``` $ sed 's/test/another test/g' myfile ``` Как видно из результатов вывода, такая команда заменила все вхождения шаблона в тексте. ![[b9e655342c3c79d46451f3f6009e4112.png]] _Глобальная замена_ Флаг команды замены `p` позволяет выводить строки, в которых найдены совпадения, при этом ключ `-n`, указанный при вызове sed, подавляет обычный вывод: ``` $ sed -n 's/test/another test/p' myfile ``` Как результат, при запуске sed в такой конфигурации на экран выводятся лишь строки (в нашем случае — одна строка), в которых найден заданный фрагмент текста. ![[4af7e88bb859d3ec2d13c8930dcbeb0a.png]] _Использование флага команды замены p_ Воспользуемся флагом `w`, который позволяет сохранить результаты обработки текста в файл: ``` $ sed 's/test/another test/w output' myfile ``` ![[b6595daff5c4d194dd9dc1788a275d22.png]] _Сохранение результатов обработки текста в файл_ Хорошо видно, что в ходе работы команды данные выводятся в [STDOUT](https://habrahabr.ru/company/ruvds/blog/326594/), при этом обработанные строки записываются в файл, имя которого указано после `w`. ## Символы-разделители Представьте, что нужно заменить `/bin/bash` на `/bin/csh` в файле `/etc/passwd`. Задача не такая уж и сложная: ``` $ sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd ``` Однако, выглядит всё это не очень-то хорошо. Всё дело в том, что так как прямые слэши используются в роли символов-разделителей, такие же символы в передаваемых sed строках приходится экранировать. В результате страдает читаемость команды. К счастью, sed позволяет нам самостоятельно задавать символы-разделители для использования их в команде замены. Разделителем считается первый символ, который будет встречен после `s`: ``` $ sed 's!/bin/bash!/bin/csh!' /etc/passwd ``` В данном случае в качестве разделителя использован восклицательный знак, в результате код легче читать и он выглядит куда опрятнее, чем прежде. ## Выбор фрагментов текста для обработки До сих пор мы вызывали sed для обработки всего переданного редактору потока данных. В некоторых случаях с помощью sed надо обработать лишь какую-то часть текста — некую конкретную строку или группу строк. Для достижения такой цели можно воспользоваться двумя подходами: - Задать ограничение на номера обрабатываемых строк. - Указать фильтр, соответствующие которому строки нужно обработать. Рассмотрим первый подход. Тут допустимо два варианта. Первый, рассмотренный ниже, предусматривает указание номера одной строки, которую нужно обработать: ``` $ sed '2s/test/another test/' myfile ``` ![[de2bc0f0ff9ed238085298bdde0e0b5e.png]] _Обработка только одной строки, номер который задан при вызове sed_ Второй вариант — диапазон строк: ``` $ sed '2,3s/test/another test/' myfile ``` ![[782882bed5d8f69312c9ba035fb84f2b.png]] _Обработка диапазона строк_ Кроме того, можно вызвать команду замены так, чтобы файл был обработан начиная с некоей строки и до конца: ``` $ sed '2,$s/test/another test/' myfile ``` ![[64b51fad408d00d803f80fc8c6ec7859.png]] _Обработка файла начиная со второй строки и до конца_ Для того, чтобы обрабатывать с помощью команды замены только строки, соответствующие заданному фильтру, команду надо вызвать так: ``` $ sed '/likegeeks/s/bash/csh/' /etc/passwd ``` По аналогии с тем, что было рассмотрено выше, шаблон передаётся перед именем команды `s`. ![[51f53115d810706487f9affff1281994.png]] _Обработка строк, соответствующих фильтру_ Тут мы использовали очень простой фильтр. Для того, чтобы в полной мере раскрыть возможности данного подхода, можно воспользоваться регулярными выражениями. О них мы поговорим в одном из следующих материалов этой серии. ## Удаление строк Утилита sed годится не только для замены одних последовательностей символов в строках на другие. С её помощью, а именно, используя команду `d`, можно удалять строки из текстового потока. Вызов команды выглядит так: ``` $ sed '3d' myfile ``` Мы хотим, чтобы из текста была удалена третья строка. Обратите внимание на то, что речь не идёт о файле. Файл останется неизменным, удаление отразится лишь на выводе, который сформирует sed. ![[05ad703f73023686c4c9ae7dad126252.png]] _Удаление третьей строки_ Если при вызове команды `d` не указать номер удаляемой строки, удалены будут все строки потока. Вот как применить команду `d` к диапазону строк: ``` $ sed '2,3d' myfile ``` ![[0a58ab5be14c11121d761c7e8973dd1d.png]] _Удаление диапазона строк_ А вот как удалить строки, начиная с заданной — и до конца файла: ``` $ sed '3,$d' myfile ``` ![[48ce22f12b835645a7b53e138d5591c8.png]] _Удаление строк до конца файла_ Строки можно удалять и по шаблону: ``` $ sed '/test/d' myfile ``` ![[e7eed5e18cac4a22642ed4fce029615d.png]] _Удаление строк по шаблону_ При вызове `d` можно указывать пару шаблонов — будут удалены строки, в которых встретится шаблон, и те строки, которые находятся между ними: ``` $ sed '/second/,/fourth/d' myfile ``` ![[7063df7b072328aa6c32bc4701c13a2a.png]] _Удаление диапазона строк с использованием шаблонов_ ## Вставка текста в поток С помощью sed можно вставлять данные в текстовый поток, используя команды `i` и `a`: - Команда `i` добавляет новую строку перед заданной. - Команда `a` добавляет новую строку после заданной. Рассмотрим пример использования команды `i`: ``` $ echo "Another test" | sed 'i\First test ' ``` ![[be96cd803c49925b9516b989f902b3de.png]] _Команда i_ Теперь взглянем на команду `a`: ``` $ echo "Another test" | sed 'a\First test ' ``` ![[10d8b8b4f5a0ce85030f0e5563db8127.png]] _Команда a_ Как видно, эти команды добавляют текст до или после данных из потока. Что если надо добавить строку где-нибудь посередине? Тут нам поможет указание номера опорной строки в потоке, или шаблона. Учтите, что адресация строк в виде диапазона тут не подойдёт. Вызовем команду `i`, указав номер строки, перед которой надо вставить новую строку: ``` $ sed '2i\This is the inserted line.' myfile ``` ![[0946be4ef914bf39059fd01eb97bd948.png]] _Команда i с указанием номера опорной строки_ Проделаем то же самое с командой `a`: ``` $ sed '2a\This is the appended line.' myfile ``` ![[b800d3eb18a27f331d8fc9b5b3f014c2.png]] _Команда a с указанием номера опорной строки_ Обратите внимание на разницу в работе команд `i` и `a`. Первая вставляет новую строку до указанной, вторая — после. ## Замена строк Команда `c` позволяет изменить содержимое целой строки текста в потоке данных. При её вызове нужно указать номер строки, вместо которой в поток надо добавить новые данные: ``` $ sed '3c\This is a modified line.' myfile ``` ![[46388b3e27486dc3ec57e7286529563c.png]] _Замена строки целиком_ Если воспользоваться при вызове команды шаблоном в виде обычного текста или регулярного выражения, заменены будут все соответствующие шаблону строки: ``` $ sed '/This is/c This is a changed line of text.' myfile ``` ![[3e9f61112ede9cd01bc9258449a2bb76.png]] _Замена строк по шаблону_ ## Замена символов Команда `y` работает с отдельными символами, заменяя их в соответствии с переданными ей при вызове данными: ``` $ sed 'y/123/567/' myfile ``` ![[08f6e3fff71b4d8ff29eeb861f45f87e.png]] _Замена символов_ Используя эту команду, нужно учесть, что она применяется ко всему текстовому потоку, ограничить её конкретными вхождениями символов нельзя. ## Вывод номеров строк Если вызвать sed, использовав команду `=`, утилита выведет номера строк в потоке данных: ``` $ sed '=' myfile ``` ![[8cf93dc7dbfff532d258cf84f92dd990.png]] _Вывод номеров строк_ Потоковый редактор вывел номера строк перед их содержимым. Если передать этой команде шаблон и воспользоваться ключом sed `-n`, выведены будут только номера строк, соответствующих шаблону: ``` $ sed -n '/test/=' myfile ``` ![[cba74750fb26c13f9c91d5ebaa14c3bb.png]] _Вывод номеров строк, соответствующих шаблону_ ## Чтение данных для вставки из файла Выше мы рассматривали приёмы вставки данных в поток, указывая то, что надо вставить, прямо при вызове sed. В качестве источника данных можно воспользоваться и файлом. Для этого служит команда `r`, которая позволяет вставлять в поток данные из указанного файла. При её вызове можно указать номер строки, после которой надо вставить содержимое файла, или шаблон. Рассмотрим пример: ``` $ sed '3r newfile' myfile ``` ![[c79cbf3adeabd2b1fd103ce59a8d27fe.png]] _Вставка в поток содержимого файла_ Тут содержимое файла `newfile` было вставлено после третьей строки файла `myfile`. Вот что произойдёт, если применить при вызове команды `r` шаблон: ``` $ sed '/test/r newfile' myfile ``` ![[cbdbb8b9db2f43d1f468db24ccf0647e.png]] _Использование шаблона при вызове команды r_ Содержимое файла будет вставлено после каждой строки, соответствующей шаблону. ## Пример Представим себе такую задачу. Есть файл, в котором имеется некая последовательность символов, сама по себе бессмысленная, которую надо заменить на данные, взятые из другого файла. А именно, пусть это будет файл `newfile`, в котором роль указателя места заполнения играет последовательность символов `DATA`. Данные, которые нужно подставить вместо `DATA`, хранятся в файле `data`. Решить эту задачу можно, воспользовавшись командами `r` и `d` потокового редактора sed: ``` $ sed '/DATA>/ { r newfile d}' myfile ``` ![[5ac3624e55a40dad7ac8d542f35eb934.png]] _Замена указателя места заполнения на реальные данные_ Как видите, вместо заполнителя `DATA` sed добавил в выходной поток две строки из файла `data`. ## Итоги Сегодня мы рассмотрели основы работы с потоковым редактором sed. На самом деле, sed — это огромнейшая тема. Его изучение вполне можно сравнить с изучением нового языка программирования, однако, поняв основы, вы сможете освоить sed на любом необходимом вам уровне. В результате ваши возможности по обработке с его помощью текстов будет ограничивать лишь воображение. На сегодня это всё. В следующий раз поговорим о языке обработки данных awk. Уважаемые читатели! А вы пользуетесь sed в повседневной работе? Если да — поделитесь пожалуйста опытом.