Утилита awk
является примером классического приложения Linux для обработки текста. Она весьма универсальна и эффективна, хоть и не предоставляет полноценный язык программирования. Однако будьте уверены, что ее возможностей вполне хватит для решения многих задач автоматизированной обработки текста (особенно при комбинировании с другими консольными утилитами).
Если 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
позволяет отбирать строки из текста на основе регулярных выражений и некоторых числовых условий.
Например, чтобы получить все строки файла 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
Соответствующий вывод:
На сайте http://tojdestvo.ru коттеджный поселок крым.
#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
имеется прямой аналог функции 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]