sed и awk (учебное пособие)I. Потоковый редактор sedКоманда имеет формат: sed [ -n ] [ -e script ] [ -f sfile ] [ files ] Команда копирует файлы (по умолчанию со стандартного входа) на стандартный выход, редактирует их в соответствии со своими(!) командами, размещенными в "script" (в командном файле или строке редактора [а не shell!]). По флагу "-f" берет берет файл команд из файла "sfile"; Если есть только опция "-e script", то флаг "-e" можно опустить. Флаг "-n" подавляет вывод (происходящий по умолчанию). "script" состоит из команд редактирования, по одной в строке, имеющих формат: [ addr [ , addr ] ] cmd [ args ] "sed" циклически преобразует входные строки в выходные. Адреса "[ addr [ , addr ] ]" - это либо номера строк, либо последняя строка (символ "$"), либо регулярные выражения в стиле редактора "ed":
Для следующих (основных) функций (команд) максимальное число допустимых адресов указано в скобках. (1)a\ text Добавляет text после указанной строки Команда: whoРезультат: root tty1 Mar 13 17:23 mas tty2 Mar 13 18:50 sae tty6 Mar 13 17:24 sae tty5 Mar 13 17:24Пример: who | sed '2a\ новая строка 'Результат: root tty1 Mar 13 17:23 mas tty2 Mar 13 18:50 новая строка sae tty6 Mar 13 17:24 sae tty5 Mar 13 17:24 (2)b label Осуществляет переход к команде ("cmd") "label:cmd". Если метка ("label") отсутствует, то переход на конец командного файла. Пример: who | sed '2a\ новая строка b lb 2d : lb 3d 'Результат: root tty1 Mar 13 17:23 mas tty2 Mar 13 18:50 новая строка sae tty5 Mar 13 17:24 (2)c\ text Удаляет выбранные строки и заменяет их на "text'. Пример: who | sed '/sae/ c\ cтрока замены 'Результат: root tty1 Mar 13 17:23 mas tty2 Mar 13 18:50 строка замены строка замены (2)d Удаляет найденные строки Пример: who | sed '2,4d'Результат: root tty1 Mar 13 17:23 (1)i\ text Вставляет "text" на место выбранной строки. (сравните с "a\") Пример: who | sed '2i\ новая строка 'Результат: root tty1 Mar 13 17:23 новая строка mas tty2 Mar 13 18:50 sae tty6 Mar 13 17:24 sae tty5 Mar 13 17:24 (2)p Выводит найденные строки (используется с флагом "-n"). (1)q Выходит из "sed". (2)r rfile Читает файл "rfile" и выдает на выход. (2)s/reg_expr/rpl/flags Заменяет регулярное выражение "reg_expr" на "rpl" с учетом флагов "flags":
Пример: who | sed 's/t/T/' echo who | sed 's/t/T/g'Результат: rooT tty1 Mar 13 17:23 mas Tty2 Mar 13 18:50 sae Tty6 Mar 13 17:24 sae Tty5 Mar 13 17:24 rooT TTy1 Mar 13 17:23 mas TTy2 Mar 13 18:50 sae TTy6 Mar 13 17:24 sae TTy5 Mar 13 17:24 (2)y/str1/str2/ Заменяет все вхождения символов "str1" соответствующими символами "str2". Длины строк должны быть одинаковыми. Пример: who | sed 'y/sae/SAE/'Результат: root tty1 MAr 13 17:23 mAS tty2 MAr 13 18:50 SAE tty6 MAr 13 17:24 SAE tty5 MAr 13 17:24 (2)! Cmd Команда(ы) "cmd" применяются к невыбранным строкам. Пример: who | sed '2,4!d'Результат: mas tty2 Mar 13 18:50 sae tty6 Mar 13 17:24 sae tty5 Mar 13 17:24 (1)= Выдает номера строк. Пример: who | sed =Результат: 1 root tty1 Mar 13 17:23 2 mas tty2 Mar 13 18:50 3 sae tty6 Mar 13 17:24 4 sae tty5 Mar 13 17:24 (2){ } Скобки группируют команды. II. Язык обработки шаблонов awk
awk - команда контекстного поиска и преобразования текста. Она - фильтр. Ее можно рассматривать как оболочку "awk" в оболочке "shell". 1. Структура awk-программыПрограмма состоит из операторов (правил), имеющих вид: шаблон {действие} шаблон {действие} . . . Частные случаи:
Действие может состоять из последовательности операторов, разделяемой ";" или переводом строки или закрывающей скобкой. Возможны комментарии (как в shell "#........."). Пример:
awk '{print}' f-awk # выдает весь текст; echo awk '/до/ {print}' f-awk # выдает строки, где есть "до". echo awk '/до/ {}' f-awk # выдает строки, где есть "до" echo awk '/до/ {print("Привет!")}' f-awkРезультат: Иванов И.И. 1980 50 Петров А.В. 1979 40 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 Привет! Привет! Существует два оператора специального вида ("BEGIN"-начальные установки и "END" - "последействия"): BEGIN {действие} шаблон {действие} шаблон {действие} . . . END {действие} 2. Вызов awkВозможны два основных варианта: 1) awk [-Fc] 'prog.awk' [files] Это простейший случай, когда программа (заключенная в кавычки " ' ") находится в теле команды, "-Fc" - флаг, меняющий стандартный разделитель полей на "c" "file" - имя файла исходных данных, при его отсутствии - со стандартного входа. (Этот формат использован в начальных примерах). cat f-awk | awk '/до/ {print}' и awk '/до/ {print}' < f-awk дают результат, аналогичный awk '/до/ {print}' f-awk Для демонстрации действия флага "-Fc" рассмотрим вызовы: awk '/до/ {print($2)}' f-awk awk -F0 '/до/ {print($2)}' f-awk На экран будет выведено: С.К. И.Х. 6 Первая команда "awk" выведет вторые поля (благодаря позиционной переменной "$2") строк, содержащие "до". (Кстати, позиционная переменная "$0" соответсвует всей строке). Во втором случае, благодаря флагу "-F" стандартные разделители заменены на символ "0", т.е. теперь выбранные строки воспринимаются, как разбитые на следующие поля: Сидоров С.К. 1979 40 ---------------------^-------------------- 1-е поле 2-е поле (пусто) Хведоров И.Х. 1970 60 -----------------^---^-------------------- 1-е поле 2-е 3-е поле (пусто) 2) awk [-Fc] -f prog.awk [files] Флаг "-f" говорит о том, что awk-программу надо брать из файла, имя которого указано следом (имя может быть произвольным и расширение ".awk" добавлено здесь просто из эстетических соображений). 3. awk-переменные и выраженияВ языке awk выделяются две группы переменных: предопределенные и декларированные в программе. Исходные значения предопределенных переменных устанавливаются интерпретатором awk в процессе запуска и выполнения awk-программы.
Прочим переменным пользователь может присваивать начальные значения. По умолчанию "0" или пустая строка (что здесь равнозначно!). Типы переменных:
Интерпретатор awk рассматривает переменную как строковую, пока не возникает необходимость выполнить операции:
Примеры: Результат: 198050 197940 197940 197060 2030 2019 2019 2030Результат: 4 4 2 0 4.2 Массив не об'является, а начинае существовать в момент первого использования. Индекс массива - любое ненулевое значение или строка. Массивы ассоциативные, т.е. не по вычисляемому индексу, а по совпадению содержания, например: day [Jan][31] = Mon day [Feb][01] = Tue day [Feb][02] = Wed Массивы удобно использовать при суммированиях, например записи выплат имеют вид (файл "p-1"): John 100 Mary 200 Mary 200 John 100 John 300 awk '{sum[$1] += $2; print $1 sum[$1]} ' < p-1 Результат (поименный нарастающий итог): John100 Mary200 Mary400 John200 John500
4. Примеры awk-программ1) awk '{print ($2, $3)}' f-awkРезультат: И.И. 1980 А.В. 1979 С.К. 1979 И.Х. 1970 2) awk '/е/ {print ($2, $3)}' f-awkРезультат: А.В. 1979 И.Х. 1970 3) awk '/е/ {print ($1, 2000 - $3)}' f-awkРезультат: Петров 21 Хведоров 30 4) awk '{ s = s + $4} END {print ("Суммарный возраст:" s) print ("Средний возраст:" s/NR)}' f-awkРезультат: Суммарный возраст:190 Средний возраст:47.5 5) awk '{ s += $4 } {print("NR="NR, "NF="NF)} END {print ("FILENAME=" FILENAME) print ("Значение позиционной переменной" $4 "\"пусто\" \ после окончания просмотра)") print ("Суммарный возраст:" s) print ("Средний возраст:" s/NR)}' f-awkРезультат: NR=1 NF=4 NR=2 NF=4 NR=3 NF=4 NR=4 NF=4 FILENAME=f-awk Значение позиционной переменной"пусто" (после окончания просмотра) Суммарный возраст:190 Средний возраст:47.5 5. СелекторыЗдесь "селектор" следует понимать, как расширение понятия "шаблон", поскольку там где в структуре команды указан шаблон, в общем случае может стоять любой селектор. Замечание. Открывающая скобка действия "{" должна быть в строке селектора. В качестве селектора может быть:
Соответствующие примеры: 1) $3 != $4 && $3 > 1970 $3 % 2 == 1 $1=="Иванов" - кавычки, чтобы воспринималось, как строка. 2) /ab/ отлично от /a b/, / ab/ и /ab / Nполя ^шаблон - по совпадению Nполя !^шаблон - по несовпадениюПример: awk '$3~0 {print} ' < f-awk echo awk '$3!~0 {print} ' < f-awk Иванов И.И. 1980 50 Хведоров И.Х. 1970 60 Петров А.В. 1979 40 Сидоров С.К. 1979 40 3) Шаблон может формировать множество образцов или указывать, в каком месте поля искать:
Примеры сочетаний: awk ' $3~/(7[0-9])$/ {print} ' f-awkРезультат: Петров А.В. 1979 40 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 То есть в третьем поле выделить 70-е годы (7 и еще одна цифра от конца поля). 6. Еще примеры1) awk '$1=="Иванов" {print} ' f-awkРезультат: Иванов И.И. 1980 50 2) awk '$4/2==30 {print} ' f-awkРезультат: Хведоров И.Х. 1970 60 3) awk '$3 != $4 && $3 > 1970 {print} ' f-awkРезультат: Иванов И.И. 1980 50 Петров А.В. 1979 40 Сидоров С.К. 1979 40 4) awk '$1~/нов$/ {print} ' f-awkРезультат: Иванов И.И. 1980 50 5) awk '/^Ив|дор/ {print} ' f-awkРезультат: Иванов И.И. 1980 50 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 6) awk '/1980/,/1979/ {print} ' f-awkРезультат: Иванов И.И. 1980 50 Петров А.В. 1979 40 7. ДействияВ awk возможны следующие действия:
Операторы управленияПростейшие операторы
Структурные операторыif (условие) {операторы} [else {операторы}] while (условие) {операторы} for (выражение; условие; выражение) {операторы} for (индекс in имя_массива) {операторы} Структурные операторы в значительной степени аналогичны соответствующим операторам Си. В последнем случае для каждого индекса выполняется блок. Текстовые индексы рассматриваются в лексикографическом порядке. Примеры 1) awk ' $4~/40/ {if($3<=1980) {print("Фамилия: " $1 ) M["40"]++}} $4~/50/ {M["50"]++} END {for(i in M) {print(" i =" i " M[" i "]=" M[i])}} ' f-awkРезультат: Фамилия: Петров Фамилия: Сидоров i =40 M[40]=2 i =50 M[50]=1 2) awk ' BEGIN {ORS = " "} { for(k=NF; k>0; --k) {print $k} {print RS} } ' f-awk | sed 's/^ //'Результат: 50 1980 И.И. Иванов 40 1979 А.В. Петров 40 1979 С.К. Сидоров 60 1970 И.Х. Хведоров Здесь, кроме изменения очередности полей в строке на противоположное (что делает цикл "for"), предварительно устанавливается выходной разделитель - пробел и весь результат предварительно выдается в одну строку, поэтому после обработки каждой строки выдается команда "print RS" для перевода выходной строки. Редактор "sed" подключен через конвейер, чтобы убрать возможные пробелы в начале строки. Существенная деталь. Если запустить лишь базовую структуру awk '{ for(k=NF; k>0; --k) {print $k}}' f-awk то все поля исходной таблицы с изменениями порядка внутри прежних строк получим вытянутыми в один столбец переводом строки: 50 1980 И.И. Иванов 40 1979 А.В. Петров 40 1979 С.К. Сидоров 60 1970 И.Х. Хведоров Однако, если поставим ";" сразу после условия, т.е. сделаем пустое тело цикла, за пределы которого вынесен "print $k" awk '{ for(k=NF; k>0; --k); {print $k}}' f-awk то получим исходную таблицу Иванов И.И. 1980 50 Петров А.В. 1979 40 Сидоров С.К. 1979 40 Хведоров И.Х. 1970 60 поскольку "$k" после выхода из цикла будет иметь значение "0", а "$0" - соответсвует всей строке в качестве значения(!), то "print $k" будет после каждого цикла печатать полные строки. 8. Ввод и вывод данныхВ общем случае в команде awk может быть указано несколько файлов. Напомним форматы вызова команды: awk [-Fc] 'prog.awk' [file ...] awk [-Fc] -f prog.awk [file ...] ^ Файлы обрабатываются последовательно в указанном порядке. Это можно использовать для "настройки" awk команды при обработке последующих файлов. Пусть файл "f0" имеет вид: 60 Сидоров А файл awk-программы "prim.awk" имеет вид: FILENAME == "f0" { # если просматривается файл "f0" w1 = $2 # присваиваются значения переменным w2 = $1 # w1 - Сидоров, w2 - 60 } $1 == w1 { print ("фамилия: "$1)} # означенные переменные $4 == w2 { print ("годы: " $4)} # используются в # селекторах Тогда при вызове команды awk -f prim.awk f0 f-awkРезультат: фамилия: Сидоров годы: 60 То есть второе поле файла "f0" дает значение переменной "w1", а первое - "w2". Эти переменные используются в селекторах при обработке файла "f-awk". Изменим программу в файле "f-awk": FILENAME == "f0" { w1 = $2 w2 = $1 next } { print ("фамилия: "$1); next} $4 == w2 { print ("годы: " $4)}Результат: фамилия: Иванов фамилия: Петров фамилия: Сидоров фамилия: Хведоров Если исключить первый оператор "next", то в выходном файле появится дополнительно первая строка: фамилия: 60 поскольку выбирается снова первое поле в певом файле ("f0"). Если исключить и второй "next", то в выходном файле появится дополнительно последняя строка: годы: 60 которая ранее не выводилась, так как в предшествующий оператор " { print ("фамилия: "$1)}" заканчивал работу на ПОСЛЕДНЕЙ строке файла "f-awk", поэтому "next" пропускал последующую командную строку $4 == w2 { print ("годы: " $4)} И еще одна модификация в связи с вводом данных с терминала. Вызов команды будет: awk -f prim.awk f0 f-awk А файл "prim.awk" примет вид: BEGIN { print ("Введите годы и фамилию: ")} FILENAME == "-" { w1 = $2 w2 = $1 next } $1 == w1 { print ("фамилия: "$1); next} $4 == w2 { print ("годы: " $4)} 9. Встроенные функцииВстроенные функции:
Примеры. 1) awk ' BEGIN {FS = "."; a=0} length ($1) > 8 {print (length ($1), $0); a++ } END {print ("Найдено строк: " a) }' f-awkРезультат: 9 Сидоров С.К. 1979 40 10 Хведоров И.Х. 1970 60 Найдено строк: 2 Здесь поля разделяются по ".", выбираются строки у которых длина первого поля больше 8-ми, и их длина "length ($1)" печатается перед строкой "$0". 2) awk '{i=split($0, Name, "."); for (j=1; j<=i; j++) print ("Name[" j "]=" Name[j]) }' f-awkРезультат: Name[1]=Иванов И Name[2]=И Name[3]= 1980 50 Name[1]=Петров А Name[2]=В Name[3]= 1979 40 Name[1]=Сидоров С Name[2]=К Name[3]= 1979 40 Name[1]=Хведоров И Name[2]=Х Name[3]= 1970 60 3) awk '{print (length)}' f-awkРезультат: 22 22 22 22 Поскольку все строки были выровнены пробелами, а в длине строки учитываются все символы до конца строки. 4) awk '{printf "%7.2f %s\n", NR, $0}' f-awk echo awk '{printf "\t%s %s \n", NR, $0}' f-awkРезультат: 1.00 Иванов И.И. 1980 50 2.00 Петров А.В. 1979 40 3.00 Сидоров С.К. 1979 40 4.00 Хведоров И.Х. 1970 60 1 Иванов И.И. 1980 50 2 Петров А.В. 1979 40 3 Сидоров С.К. 1979 40 4 Хведоров И.Х. 1970 60 |