А АWednesday, 21 October 2020

Sed - потоковый редактор текстовых файлов.


Всем привет.

Говорят что bash без применения утилит обработки текста sed и awk такой же скучный как осенний дождливый день. Однако чтобы разогнать тучи надо совсем немного - научиться понимать простейшие конструкции sed и awk и нескольких базовых команд.

Мы напомним что sed это неинтерактивный редактор текстовых файлов, а аwk - это язык обработки шаблонов с C-подобным синтаксисом. 

При всех своих различиях, эти две утилиты обладают похожим синтаксисом, они обе умеют работать с регулярными выражениями, обе, по-умолчанию, читают данные с устройства stdin и обе выводят результат обработки на устройство stdout. Обе являются утилитами UNIX-систем, и прекрасно могут взаимодействовать между собой. Вывод от одной может быть перенаправлен, по конвейеру, на вход другой. Их комбинирование придает сценариям, на языке командной оболочки, мощь и гибкость языка Perl.

Сегодня мы рассмотрим sed, а аwk будет позже. 

Базис.

Итак sed - потоковый редактор, который работает со строками. Параметры его потоков это:

* строка на входе (Input Stream)

* строка в оперативной ячейке (Pattern Space)

* буфер, в который можно поместить или добавить строку (Hold Buffer)

* строка на выходе (Output Stream)


Все команды sed - это буквы:

a b c d D g G h H i n N p P q Q r s t w x y =

Каждая - определяет некоторую операцию. Как правило, большинство команд sed применяются

к оперативной ячейке и выходному потоку. Т.е. если мы напишем что-то типа

sed -r 's/file/replace/' file.txt,

то подразумевается, что замена происходит для строки, находящейся в оперативной ячейке.


Для входного потока действуют только две команды:

n - взять строку и поместить ее в оперативную ячейку

N - взять строку и добавить ее к оперативной ячейке


На буфер влияют еще три команды

h - поместить оперативную ячейку в буфер

H - добавить оперативную ячейку к буферу

x - обменять местами буфер и оперативную ячейку


На выходной поток влияют команды:

= - напечатать номар строки

a - добавить текст после строки вывода

i - добавить текст до строки вывода

c - заменить текст вывода на указанный

p - вывести текущую оперативную ячейку

P - вывести первую строку из оперативной ячейки

r - добавить текст из файла

R - добавить строку из файла (GNU)

w - записать оперативную ячейку в файл

W - записать первую строку из оперативной ячейки в файл


К оперативной ячейке можно применить команды:

d - удалить содержимое оперативной ячейки. начать новый цикл

D - удалить первую строку из оперативной ячейки.

    начать новый цикл, но вход не брать, пока ячейка не пуста

g - скопировать буфер в оперативную ячейку

G - добавить буфер к оперативной ячейке

s - произвести замену

y - произвести транслитерацию


И есть еще условные и управляющие команды, позволяющие организовывать логику:

{} - определить блок команд

b  - перейти на метку

t  - перейти на метку или в конец скрипта, если предыдущий оператор s успешен

T  - перейти на метку или в конец скрипта, если предыдущий оператор s не успешен

q  - завершить скрипт, но перед этим допечатать то, что осталось на входе (GNU)

Q  - завершить скрипт немедленно


Есть также как минимум два важных ключа, влияющих на работу и удобство использования sed:

sed -n ...  - подавить автоматический вывод оперативной ячейки

sed -r ...  - использовать расширенный синтаксис регулярных выражений (без слешей)


Чтобы понять логику работы, рассмотрим такой простой скрипт <tt>test.sed</tt>:

#!/bin/sed -rnf

1 {

  h

  b eof

}

$ {

  =

  x

  p

  Q

}

H

:eof


Его работа заключается в том, что после вызова ./test.sed somefile он выводит на экран число строк в файле somefile, а затем все строки файла, кроме последней. Алгоритм простой:

* Если встречаем первую строку - помещаем ее в буфер (h), выходим через метку eof (b)

* Если встречаем последную строку - выводим ее номер (=), достаем всё, что есть в буфере (x),

  выводим (p), завершаем (Q)

* По умолчанию - добавляем очередную строку к буферу (H)


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

1. Нет необходимости использовать очень сложные регулярные выражения в поиске и заменах.

   Вместо одной замены - можно провести две, использовав некоторую терминальную строку

   или символ - такую последовательность, которой заведомо нет в исходном файле

   и которая не нарушит работу.


# Плохо

s/очень сложное выражение/замена/

# Хорошо

s/менее сложное выражение/~/; s/~/замена/


2. Одинаковые символы, слова и строки можно искать одним простым выражением

# Найти два одинаковых символа в строке

/(.)1/

# Найти две одинаковых строки

/(.*)n1/


3. Анализ содержимого строки можно делать операцией <tt>s/...//;T</tt>

# если в строке есть 3 слова linux - то отметить такие строки !

sed -r 's/(linux).*1.*1//;T;s/.*/!/' file.txt


4. Поскольку часто приходится рабоать с таблицами, то доступ к колонке

   также можно получать простым выражением:

# Удалить первые три колонки

s/(S+s+){3}//

# Оставить четвертую и пятую

s/(S+s+){2}.*/1/


5. Очень многие разные задачи решаются одним и тем же приемом - G-s-h-p

   Вот, к примеру, скрипт, который из списка слов выдаёт только уникальные слова:

#!/bin/sed -rnf

G; s/(^[^n]+n)(.*)1/12/; h

$ {

  p

}


Эмуляция известных команд в sed.

Эмуляция grep pattern

sed -n '/pattern/p'

sed '/regexp/!d'


Эмуляция grep -v pattern

sed -n '/pattern/!p'


Эмуляция egrep

sed -e '/AAA/b' -e '/BBB/b' -e '/CCC/b' -e d


Эмуляция head

sed 10q


Эмуляция head -n 1

sed q


Эмуляция tail

sed -e :a -e '$q;N;11,$D;ba'


Эмуляция tail -n 1

sed '$!d'

sed -n '$p'


Эмуляция tail -n 2

sed '$!N;$!D'


Эмуляция cat -s

sed '/./,/^$/!d'


Эмуляция wc -l (Счетчик строк)

sed -n '$='


Эмуляция tac (Реверсивный cat)

sed '1!G;h;$!d'

sed -n '1!G;h;$p'


Эмуляция nl (нумерация)

sed = filename | sed 'N;s/n/t/'


Эмуляция rev (переворот строки)

sed '/n/!G;s/(.)(.*n)/&21/;//D;s/.//'


Эмуляция uniq (уникальные строки)

sed '$!N;/^(.*)n1$/!P; D'


Эмуляция uniq -d (дубли строк)

sed '$!N;s/^(.*)n1$/1/;t;D'


Рецепты sed.

Как и в случае с другими командами - главное для sed - это опыт, который можно получить только практикой. А практика начинается с обычного текстового файла, который можно создать в обычном консольном редакторе типа nano и затем использовать как материал для sed. У меня это выглядело так:


# исходный файл

cat file.txt | nl

 1 москва

 2 питер

 3 санкт петербург

 4 киев

 5 минск

 6 новгород

 7 уфа

 8 казань

 9 иркутск

10 сергиев посад

11 павловский посад

12 калязин

13 углич

14 самара

15 владимир

16 суздаль

17 кострома


Дальше я начал свои эксперименты:


# вывести строку N3

sed -n 3p file.txt


# удалить строку N3 (cанкт петербург)

sed 3d file.txt


# вывести "посады"

sed -n '/посад/p' file.txt


# вывести города на "са"

sed -n '/^са/p' file.txt


# удалить пустые строки

sed '/^$/d' file.txt


# удалить все строки "посад"

sed '/посад/d' file.txt


# удалить все слова "посад" в строках

sed 's/посад//g' file.txt


# удалить все строки до первого "посад"

sed 1,'/посад/d' file.txt


# удалить все строки до первой пустой

sed 1,'/^$/d' file.txt


# заменить посад на ПОСАД

sed 's/посад/ПОСАД/' file.txt


# заменить только павлов посад на ПОСАД

sed '/павл/s/посад/ПОСАД/' file.txt


# в каждой строке заменить все "о" на "О"

sed 's/о/0/g' file.txt


# удалить все пробелы в конце строки

sed 's/ *$//' file.txt


Я не стал накручивать большие регулярные выражения - но это возможно и легко, поскольку параметры основных команд sed (p,d,s,y) - могут являться регулярными выражениями. Продолжаем изыскания:


# добавляем пустые строки

sed G file.txt


# удалить каждую третью строку

sed 'n;n;d' file.txt


# количество строк в файле

sed -n '$=' file.txt


# добавляем строки с номерами

sed = file.txt


# удаляем пробелы и табуляции с конца и с начала

sed 's/^[ t]*//;s/[ t]*$//' file.txt


Фуух, вот так. Успехов.

No comments:

Post a Comment

А что вы думаете по этому поводу?

Версия на печать

Популярное