IT Notes

AWK: Примеры программ

Утилита awk является примером классического приложения Linux для обработки текста. Она весьма универсальна и эффективна, хоть и не предоставляет полноценный язык программирования. Однако будьте уверены, что ее возможностей вполне хватит для решения многих задач автоматизированной обработки текста (особенно при комбинировании с другими консольными утилитами).

Способы запуска awk-программ

Если awk-программа достаточно простая и короткая, то ее код можно набрать прямо в консоли:

awk '< код awk-программы >' < имя_файла_для_обработки >

В качестве входных данных для awk можно использовать не только текстовые файлы, но и вывод в стандартный поток других приложений:

< некое_приложение > | awk '< код awk-программы >'

В случае, когда код awk-программы достаточно объемный или должен быть сохранен для повторного использования, его можно вызывать из файла с ключом -f:

awk -f < имя_файла_с_кодом_awk_программы > < имя_файла_для_обработки >

Для проведения экспериментов используем файл test.cpp, на котором будем проверять результаты работы awk- программ:

#include <iostream>
#include <string>
#include <vector>

void test1();
int test2();

// Комментарий в стиле С для функции main()
int main( int argc, char** argv ) {
    std::cout << "Hello, world!" << std::endl;

    for( int i = 0; i < 10; ++i ) {
        std::cout << i << std::endl;
    }

    return 0;
}

// Комментарий в стиле С для функции test1()
void test1() {
    std::cout << "Hello, test1!" << std::endl;
}

// Комментарий в стиле С для функции test2()
int test2() {
    std::cout << "Hello, test2!" << std::endl;
}

Фильтрация строк с помощью awk

В первую очередь awk позволяет отбирать строки из текста на основе регулярных выражений и некоторых числовых условий.

Отбор строк, соответствующих регулярному выражению

Например, чтобы получить все строки файла test.cpp, содержащие директиву препроцессора #include, воспользуемся следующей командой:

awk '/^#\s*include/' test.cpp

Регулярное выражение записывается между двумя символами /. В результате получим:

#include <iostream>
#include <string>
#include <vector>

Отбор строк, НЕ соответствующих регулярному выражению

Чтобы оставить все строки, которые не соответствуют регулярному выражению, воспользуйтесь командой из предыдущего подраздела и поставьте в начало awk-кода восклицательный знак. Например, так мы исключим все закомментированные строки:

awk '! /^[/]{2}.*/' test.cpp

Вот что осталось:

#include <iostream>
#include <string>
#include <vector>

void test1();
int test2();

int main( int argc, char** argv ) {
    std::cout << "Hello, world!" << std::endl;

    for( int i = 0; i < 10; ++i ) {
        std::cout << i << std::endl;
    }

    return 0;
}

void test1() {
    std::cout << "Hello, test1!" << std::endl;
}

int test2() {
    std::cout << "Hello, test2!" << std::endl;
}

Отбор строк из заданного диапазона

Определить диапазон строк для вывода на экран можно с помощью двух регулярных выражений, записанных через запятую. В качестве примера найдем определение всех функций, возвращающих int:

awk '/^int .*(.*) {/, /^}/' test.cpp

Соответствующий результат:

int main( int argc, char** argv ) {
    std::cout << "Hello, world!" << std::endl;

    for( int i = 0; i < 10; ++i ) {
        std::cout << i << std::endl;
    }

    return 0;
}
int test2() {
    std::cout << "Hello, test2!" << std::endl;
}

Комбинирование условий фильтрации

Для проверки строк сразу по нескольким условиям используйте операторы && (И) и || (ИЛИ).

Следующая команда выводит все комментарии, которые не содержат main:

awk '/[/]{2}.*/ && ! /main/' test.cpp

В итоге имеем:

индивидуалки москвы. Как продвигать сайт самостоятельно в интернете.

// Комментарий в стиле С для функции test1()
// Комментарий в стиле С для функции test2()

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

awk '4 < NR && NR < 7' test.cpp

NR - переменная awk, которая определяет номер строки. Таким образом, представленный код выводит 5-ую и 6-ую строки:

void test1();
int test2();

Отбор строк по условиям относительно отдельных слов

Awk может фильтровать текст не только по строкам, но и по отдельным словам. На i-ое слово в строке можно сослаться с помощью $i. Нумерация начинается с единицы, а $0 определяет содержимое всей строки. Количество слов в строке определяется с помощью переменной NF, поэтому $NF указывает на последнее слово. Например, найдем строки, первым словом которых является int или void:

awk '$1 == "int" || $1 == "void"' test.cpp

Соответствующий вывод на консоль:

void test1();
int test2();
int main( int argc, char** argv ) {
void test1() {
int test2() {

Однако проще использовать проверку по регулярному выражению для слова. Для этого в awk предусмотрен специальный оператор ~, который нужно поставить между переменной, указывающей на слово, и регулярным выражением. В качестве примера перепишем предыдущую команду в более компактном виде:

awk '$1 ~ /int|void/' test.cpp

Отбор строк на основе числовых характеристик

В awk доступны арифметические операторы языка C, что открывает свободу действий. Пример ниже выводит все четные строки (NR - номер строки):

awk 'NR % 2 == 0' test.cpp

Соответствующий вывод:

#include <string>

int test2();
// Комментарий в стиле С для функции main()
    std::cout << "Hello, world!" << std::endl;
    for( int i = 0; i < 10; ++i ) {
    }
    return 0;

void test1() {
}
// Комментарий в стиле С для функции test2()
    std::cout << "Hello, test2!" << std::endl;

Следующая awk-программа выводит все строки, у которых длина 1-ого слова равна трем:

awk 'length( $1 ) == 3' test.cpp

В результате получаем:

int test2();
int main( int argc, char** argv ) {
int test2() {

Далее приводится код для вывода строк, состоящих из двух слов (NF - количество слов в строке):

awk 'NF == 2' test.cpp

И соответствующий вывод:

#include <iostream>
#include <string>
#include <vector>
void test1();
int test2();
    return 0;

Работа со строками в awk

Как вы могли убедиться, awk обладает неплохим набором функций для фильтрации строк текста. Однако для этих строк еще можно выполнять различные преобразования. Команды для работы со строками должны быть обернуты в фигурные скобки { … }. Код в скобках последовательно вызывается для каждой строки обрабатываемого текста.

Форматированный вывод

В awk имеется прямой аналог функции printf() языка C. В качестве примера выведем в начале каждой строки ее номер:

awk '{ printf "%-2d %s\n", NR, $0 }' test.cpp

Вот что получили:

1  #include <iostream>
2  #include <string>
3  #include <vector>
4
5  void test1();
6  int test2();
7
8  // Комментарий в стиле С для функции main()
9  int main( int argc, char** argv ) {
10     std::cout << "Hello, world!" << std::endl;
11
12     for( int i = 0; i < 10; ++i ) {
13         std::cout << i << std::endl;
14     }
15
16     return 0;
17 }
18
19 // Комментарий в стиле С для функции test1()
20 void test1() {
21     std::cout << "Hello, test1!" << std::endl;
22 }
23
24 // Комментарий в стиле С для функции test2()
25 int test2() {
26     std::cout << "Hello, test2!" << std::endl;
27 }

Функции преобразования

Кроме printf() есть в awk и другие функции. Например, print() и toupper():

awk '{ print toupper( $0 ) }' test.cpp

Соответствующий результат:

#INCLUDE <IOSTREAM>
#INCLUDE <STRING>
#INCLUDE <VECTOR>

VOID TEST1();
INT TEST2();

// КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ MAIN()
INT MAIN( INT ARGC, CHAR** ARGV ) {
    STD::COUT << "HELLO, WORLD!" << STD::ENDL;

    FOR( INT I = 0; I < 10; ++I ) {
        STD::COUT << I << STD::ENDL;
    }

    RETURN 0;
}

// КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST1()
VOID TEST1() {
    STD::COUT << "HELLO, TEST1!" << STD::ENDL;
}

// КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST2()
INT TEST2() {
    STD::COUT << "HELLO, TEST2!" << STD::ENDL;
}

Условные конструкции

В awk-программах доступны операторы if-else. Например, следующий код выводит без изменения строки, у которых на 1-ой позиции стоит int, а на последней - {, иначе на консоль отправляется ---:

awk ' { if( $1 == "int" && $NF == "{" ) print; else print "---" }' test.cpp

Выполнение кода приводит к выводу следующего:

---
---
---
---
---
---
---
---
int main( int argc, char** argv ) {
---
---
---
---
---
---
---
---
---
---
---
---
---
---
---
int test2() {
---
---

Переменные

Доступны в awk-программах и переменные, которые не требуется предварительно объявлять. Следующий код для подсчета количества строк и слов в тексте поместим в файл stat.awk:

{
    lineCount++;
    wordCount += NF
}
END {
    printf "line count: %d, word count: %d\n", lineCount, wordCount
}

Тогда его вызов осуществляется следующим образом:

awk -f stat.awk test.cpp

Результат выполнения:

line count: 27, word count: 88

Фильтр END указывает, что код в скобках после него должен выполняться только после прохода всех строк. Доступен в awk и фильтр BEGIN, поэтому в более общем случае программа принимает вид:

BEGIN { Вызывается до начала прохода по строкам }
      { Вызывается для каждой строки после секции BEGIN, но до секции END }
END   { Вызывается после завершения прохода по строкам }

Обратите внимание, что посчитать строки и слова в тексте намного проще с помощью wc:

wc -lw test.cpp

Циклы

В awk-программах вам также доступны циклы for и while в стиле C. Для примера выведем все строки в обратном порядке. Создадим файл reverse.awk следующего содержимого:

{
    for( i = NF; i > 0; --i )
        printf "%s ", $i;
    printf "\n"
}

Вызовем программу следующий образом:

awk -f reverse.awk test.cpp

В результате слова в каждой строке будут выведены в обратном порядке:

<iostream> #include
<string> #include
<vector> #include

test1(); void
test2(); int

main() функции для С стиле в Комментарий //
{ ) argv char** argc, int main( int
std::endl; << world!" "Hello, << std::cout

{ ) ++i 10; < i 0; = i int for(
std::endl; << i << std::cout
}

0; return
}

test1() функции для С стиле в Комментарий //
{ test1() void
std::endl; << test1!" "Hello, << std::cout
}

test2() функции для С стиле в Комментарий //
{ test2() int
std::endl; << test2!" "Hello, << std::cout
}

Нестандартный разделитель слов

По умолчанию awk в качестве разделителя слов использует пробельные символы, однако такое поведение можно изменить. Для этого воспользуйтесь ключом -F, после которого укажите строку, определяющую разделитель. Например, следующая программа выводит название группы и ее пользователей (если в группе есть пользователи) из файла /etc/group, применяя в качестве разделителя символ двоеточия:

awk -F":" '{ if( $4 ) printf "%15s: %s\n", $1, $4 }' /etc/group

Комбинирование фильтров и команд печати

Все рассмотренные ранее фильтры можно использовать совместно с командами обработки строк. Достаточно записать ограничения перед фигурными скобками. Ниже представлен пример для вывода первых 9 строк вывода команды ps, содержащей информацию о пользователе, идентификаторе процесса и имени команды:

ps axu | awk 'NR < 10 { print $1, $2, $NF }'

После запуска увидим:

USER PID COMMAND
root 1 /sbin/init
root 2 [kthreadd]
root 3 [ksoftirqd/0]
root 5 [kworker/0:0H]
root 7 [rcu_preempt]
root 8 [rcu_sched]
root 9 [rcu_bh]
root 10 [migration/0]

Похожие публикации