У цьому розділі ми обговоримо:

  • що таке gawk
  • вживання команд gawk в командному рядку
  • як форматувати текст за допомогою gawk
  • як обробляти користувацькі регулярні вирази за допомогою gawk
  • gawk в сценаріях
  • gawk та змінні

Примітка: Для тих, хто хоче ще більше розважитись

Так само як і у випадку з sed, про різні версії gawk можуть бути написані цілі книги. Цьому вступові дуже далеко до повноти і він введений лише для кращого розуміння прикладів у наступних розділах. Для отримання детальнішої інформації краще всього розпочати з підручників, що поставляються разом з GNU awk: “GAWK: Effective AWK programming: A User's Guide for GNU Awk”.

Розпочинаючи роботу з gawk

Що таке gawk

gawk – це GNU версія широко відомої в світі UNIX програми awk, іншого популярного потокового редактора. Оскільки доволі часто програма awk є лише лише посиланням на gawk, далі ми будемо говорити про неї як про awk.

Основною функцією awk є пошук у файлах рядка чи іншої текстової одиниці на наявність у ньому шуканого зразка. Після знаходження такого зразка над відповідним рядком проводяться певні дії.

Програми на awk відрізняються від програм на більшості інших мов, оскільки вони є керовані даними: ви спершу описуєте дані, з якими б ви хотіли працювати, а потім – що б ви хотіли з ними зробити. Більшість інших мов є процедурними: вам потрібно дуже детально описати кожен крок, що його повинна зробити програма. Працюючи з процедурними мовами, як правило значно важче описати дані, котрі буде обробляти ваша програма. Для таких випадків програми на awk є набагато легшими в плані розуміння та розробки.

Команди gawk

Під час запуску awk ви вказуєте, яку програму потрібно запустити. Програма складається з набору правил. Вона також може містити визначення функцій, цикли, умови та інші конструкції. Кожне правило описує один взірець для пошуку, та одну дію, що потрібно буде зробити з знайденим взірцем.

Є кілька можливих шляхів запуску awk. Якщо програма коротка, простіше запустити її у командному рядку:

awk ПРОГРАМА вхідний_файл(и)

Якщо ж потрібно зробити декілька змін, можливо циклічно та в кількох файлах, тоді простіше записати команди awk в сценарій:

awk -f СЦЕНАРІЙ вхідний_файл(и)

Програма роздруку

Роздрук вибраних полів

Команда print в awk роздруковує вибрані дані з вхідного файлу.

Коли awk зчитує рядок з файлу, вона ділить його на поля, опираючись на спеціальну змінну awk – розділювач полів (FS – field separator). Стандартно вона означає один чи декілька пробілів чи табуляторів.

Змінні $1, $2, $3, ... , $N містять значення першого, другого, третього і т.д. полів вхідного рядка. Змінна $0 (нуль) містить ввесь рядок. Це проілюстровано малюнком внизу, де ми бачимо шість стовпчиків у виводу команди df:

(малюнок 6-1)

У виводі команди ls -l є 9 стовпчиків. Print використовує їх ось так:

kelly@octarine ~/test> ls -l | awk '{ print $9 $5 }'
160orig
121script.sed
120temp_file
126test
120twolines
441txt2html.sh

kelly@octarine ~/test>

Ця команда роздрукувала п'ятий стовпчик довгого списку файлів, який містить розмір файлу, та останній стовпчик – з іменем файлу. Цей вивід не є особливо читабельним, поки ви не вживаєте стандартний метод розділення стовпчиків – розділювати все, що ви хочете виводити, комами. В цьому випадку стандартний символ розділення виводу (як правило це пробіл) буде вкладений поміж кожним полем, що виводиться на друк.

Форматування полів

Без форматування, з використанням лише стандартного розділювача, вивід виглядає доволі блідо. Вставка кількох табуляторів та рядка, що пояснюватиме, що це за вивід змусить його виглядати значно краще:

kelly@octarine ~/test> ls -ldh * | grep -v total | \ 
awk '{ print "Файл " $9 " має розмір " $5 " байт."}'
Файл orig має розмір 160 байт.
Файл script.sed має розмір 121 байт.
Файл temp_file має розмір 120 байт.
Файл test має розмір 126 байт.
Файл twolines має розмір 120 байт.
Файл txt2html.sh має розмір 441 байт.

kelly@octarine ~/test>

Візьміть собі на замітку, що вживання зворотної похилої риски дозволяє розбивати довгий рядок команд на декілька; при цьому оболонка сприймає і виконує таку команду, ніби вона написана в одному рядку. В той час, як ваш командний рядок може бути необмеженої довжини, монітор та принтер такими властивостями не володіють. Вживання зворотної похилої також дозволяє копіювати та вставляти цілі рядки у вікно терміналу.

Опція -h команди ls вживається для відтворення розміру великих файлів у зручному для людини форматі. Коли аргументом є каталог, до списку файлів додається рядок з кількістю блоків, що їх займає каталог. Оскільки нам цей рядок не потрібен, то ми додаємо зірочку до опцій команди ls. З цією ж метою ми додаємо опцію -d на той випадок, якщо зірочка позначає ще й каталог.

Зворотна коса в цьому прикладі означає продовження команди у наступному рядку. Ви можете виводити будь-яке число стовпчиків і навіть змінювати порядок виведення. В нижченаведеному прикладі це демонструється для виведення найбільш зайнятих розділів:

kelly@octarine ~> df -h | sort -rnk 5 | head -3 | \ 
awk '{ print "Розділ " $6 "\t: " $5 " заповненість!" }'
Розділ /var  : 86% заповненість!
Розділ /usr  : 85% заповненість!
Розділ /home : 70% заповненість!

kelly@octarine ~>

Наведемо кілька символів особливого значення: Лапки, символ долара та інші мета-символи повинні бути екрановані зворотною рискою.

\a : Дзвоник

\n : Символ нового рядка

\t : Табулятор

Команда print та регулярні вирази

Регулярні вирази можуть застосовуватись як зразок, якщо їх оточити косими рисками. Далі вони звіряються з текстом кожного запису. Синтаксис команди такий:

awk 'РЕГУЛЯРНИЙ_ВИРАЗ {ПРОГРАМА}' файл(и)

Цей приклад показує інформацію лише про локальні диски; мережеві файлові системи не показуються:

kelly is in ~> df -h | awk '/dev\/hd/ { print $6 "\t: " $5 }'
/       : 46%
/boot   : 10%
/opt    : 84%
/usr    : 97%
/var    : 73%
/.vol1  : 8%

kelly is in ~>

Косі риски потрібно екранувати, оскільки вони мають особливе значення у програмах awk. А ось ще один приклад, в котрому ми шукаємо в каталозі /etc файли, що закінчуються на “.conf” а розпочинаються на “a” або “х”; вживаємо при цьому складніший регулярний вираз:

kelly is in /etc> ls -l | awk '/\<(a|x).*\.conf$/ { print $9 }'
amd.conf
antivir.conf
xcdroast.conf
xinetd.conf

kelly is in /etc>

Цей приклад демонструє особливе значення крапки у регулярних виразах: перша означає, що ми хочемо шукати будь-який символ після першого пошукового рядка, друга – екранується, оскільки вона є частиною рядка, що його потрібно знайти.

Спеціальні шаблони

Для того, щоб виводу передували коментарі, вживайте службове слово BEGIN:

kelly is in /etc> ls -l | \
awk 'BEGIN { print "Знайдено файли:\n" } /\<[a|x].*\.conf$/ { print $9 }'
Знайдено файли:
amd.conf
antivir.conf
xcdroast.conf
xinetd.conf

kelly is in /etc>

Відповідно службове слово END може бути додане, щоб вивести текст після обробки всієї інформації:

kelly is in /etc> ls -l | \
awk '/\<[a|x].*\.conf$/ { print $9 } END { print \
"Я можу ще щось зробити для вас?" }'
amd.conf
antivir.conf
xcdroast.conf
xinetd.conf
Я можу ще щось зробити для вас?

kelly is in /etc>

Сценарії gawk

Оскільки команди стають чим далі тим довшими, ви можете захотіти записати їх у сценарій для повторного вжитку. Сценарій awk містить команди awk, що складаються зі зразків та вказівок (дій).

Щоб проілюструвати це, підготуємо звіт, що показуватиме нам найбільш завантажені розділи:

kelly is in ~> cat diskrep.awk
BEGIN { print "*** УВАГА УВАГА УВАГА ***" }
/\<[8|9][0-9]%/ { print "Розділ " $6 "\t: " $5 " заповнений!" }
END { print "*** ТЕРМІНОВО виділіть гроші на купівлю нового диска! ***" }

kelly is in ~> df -h | awk -f diskrep.awk
*** УВАГА УВАГА УВАГА ***
Розділ /usr  : 97% заповнений!
*** ТЕРМІНОВО виділіть гроші на купівлю нового диска! ***

kelly is in ~>

awk спершу друкує ввідне повідомлення, далі форматує та виводить всі рядки, що містять вісімку чи дев'ятку на початку слова, далі – будь-яку цифру та символ відсотка і в кінці – ще одне повідомлення.

Примітка: Підсвічування синтаксису.

Awk є мовою програмування. Її синтаксис розпізнається більшістю редакторів, що можуть підсвічувати синтаксис інших мов програмування, таких як С, HTML та ін.

Змінні gawk

Оскільки awk обробляє вхідний файл, він використовує кілька змінних. Частину з них можна змінювати, інші призначені лише для читання.

Розділювач вхідних полів

Розділювач полів, що може бути як одним символом, так і регулярним виразом, визначає спосіб, у який awk ділить вхідний текст на поля. Вхідний текст переглядається в пошуках символів, що відповідають опису розділювача; поля самі по собі є текстом, що знаходиться між знайденими відповідностями.

Розділювач полів представлений вбудованою змінною FS (Field Separator). Зауважте, що це не те ж саме, що змінна IFS, котра вживається у POSIX-сумісних оболонках. Значення цієї змінної може бути змінене програмою awk за допомогою оператора присвоєння =. Як правило це робиться на самому початку програми, до того, як буде оброблений будь-який вхідний текст. Для досягнення цього вживайте службове слово BEGIN. В нижченаведеному прикладі ми формуємо команду, що показує всіх користувачів системи:

kelly is in ~> awk 'BEGIN { FS=":" } { print $1 "\t" $5 }' /etc/passwd
kelly   Kelly Smith
franky  Franky B.
eddy    Eddy White
willy   William Black
cathy   Catherine the Great
sandy   Sandy Li Wong

kelly is in ~>

В сценарії awk це може виглядати наступним чином:

kelly is in ~> cat printnames.awk
BEGIN { FS=":" }
{ print $1 "\t" $5 }

kelly is in ~> awk -f printnames.awk /etc/passwd
kelly   Kelly Smith
franky  Franky B.
eddy    Eddy White
willy   William Black
cathy   Catherine the Great
sandy   Sandy Li Wong

Уважно вибирайте розділювач вхідних полів для того, щоб уникнути зайвих проблем. Скажімо, ви отримуєте ввід у вигляді приблизно таких рядків:

Sandy L. Wong, 64 Zoo St., Antwerp, 2000X

Ви пишете команду чи сценарій, що виводитиме ім'я людини в такому записі:

awk 'BEGIN { FS="," } { print $1, $2, $3 }' inputfile

Але людина може мати звання доктора філософії і її ім'я може бути записане таким чином:

Sandy L. Wong, PhD, 64 Zoo St., Antwerp, 2000X

Ваша програма працюватиме невірно на такому рядку. Якщо потрібно, застосовуйте додаткові команди awk чи sed щоб добитись однакового вигляду ваших вхідних даних.

Розділювачі вихідних даних

Розділювач вихідних полів

Поля під час виводу розділяються пробілами. Це робиться у тому випадку, якщо ви вживаєте правильний синтаксис команди print, розділюючи її аргументи комами:

kelly@octarine ~/test> cat test
record1         data1
record2         data2

kelly@octarine ~/test> awk '{ print $1 $2}' test
record1data1
record2data2

kelly@octarine ~/test> awk '{ print $1, $2}' test
record1 data1
record2 data2

kelly@octarine ~/test>

Якщо ви не вставите коми, print виведе аргументи як одне поле, опускаючи розділювач вихідних полів. Таким розділювачем може бути будь-який рядок символів, що ви його можете призначити змінній OFS.

Розділювач вихідних записів

Вивід усієї команди print називається вихідним записом. Результатом виконання кожної команди print є запис, після котрого виводиться рядок, що називається розділювачем вихідних записів (ORS – Output Record Separator). Стандартним значенням цієї змінної є \n - символ нового рядка, тому кожна команда print формує окремий рядок. Для того, щоб змінити спосіб розділення вихідних записів та полів, достатньо присвоїти нові значення змінним OFS та ORS:

kelly@octarine ~/test> awk 'BEGIN { OFS=";" ; ORS="\n-->\n" } \
{ print $1,$2}' test
record1;data1
-->
record2;data2
-->

kelly@octarine ~/test>

Якщо значення ORS не містить символу нового рядка, ввесь вивід програми буде в одному довгому рядку.

Кількість записів

Вбудована змінна NR містить кількість оброблених записів. Вона збільшується на одниницю щоразу після зчитування нового рядка. Ви можете вживати її щоб показати кількість оброблених записів або для кожного запису зокрема:

kelly@octarine ~/test> cat processed.awk
BEGIN { OFS="-" ; ORS="\n--> done\n" }
{ print "Запис номер " NR ":\t" $1,$2 }
END { print "Всього оброблено записів: " NR }

kelly@octarine ~/test> awk -f processed.awk test
Запис номер 1:        record1-data1
--> done
Запис номер 2:        record2-data2
--> done
Всього оброблено записів: 2
--> done

kelly@octarine ~/test>

Змінні, введені користувачем

Окрім вбудованих змінних ви можете задавати свої власні. Коли awk зустрічає посилання на змінну, якої не існує (тобто, яка не визначена попередньо), змінна створюється та ініціалізується порожнім рядком. Для всіх наступних звернень значення змінної є таким, як встановлено попередньо. Змінні можуть бути рядком або числом. Вміст вхідних полів також може бути присвоєний змінним.

Значення можуть присвоюватись напряму, за допомогою оператора =, або ж ви можете вживати поточне значення змінної у комбінації з іншими операторами:

kelly@octarine ~> cat revenues
20021009        20021013        consultancy     BigComp         2500
20021015        20021020        training        EduComp         2000
20021112        20021123        appdev          SmartComp       10000
20021204        20021215        training        EduComp         5000

kelly@octarine ~> cat total.awk
{ total=total + $5 }
{ print "Відправити рахунок на " $5 " гривень до " $4 }
END { print "---------------------------------\nTotal revenue: " total }

kelly@octarine ~> awk -f total.awk test
Відправити рахунок на 2500 гривень до BigComp
Відправити рахунок на 2000 гривень до EduComp
Відправити рахунок на 10000 гривень до SmartComp
Відправити рахунок на 5000 гривень до EduComp
---------------------------------
Загальний прибуток: 19500

kelly@octarine ~>

Підтримуються також скорочені присвоювання у стилі мови С: ЗМІННА+=ЗНАЧЕННЯ.

Додаткові приклади

Приклад з параграфу 5.3.2 може бути значно легшим, якщо ми застосуємо сценарій awk:

kelly@octarine ~/html> cat make-html-from-text.awk
BEGIN { print "<html>\n<head><title>HTML, згенерований awk</title></head>\n<body bgcolor=\"#ffffff\">\n<pre>" }
{ print $0 }
END { print "</pre>\n</body>\n</html>" }

Та й команда для його запуску є значно зрозумілішою в такому випадку:

kelly@octarine ~/html> awk -f make-html-from-text.awk testfile > file.html

Примітка: Приклади вживання awk у вашій системі

Ми знову посилаємось на каталог, що містить стартові сценарії у вашій системі. Введіть команду, що наведена нижче щоб побачити більш практичні приклади вживання awk:

grep awk /etc/init.d/*

Програма printf

Для точнішого контролю над виводом ваших сценаріїв, який стандартно формується командою print, вживайте команду printf. З її допомогою можна задати ширину виводу для кожного поля, різні види форматування для чисел (наприклад, у якій системі числення виводити число, скільки знаків після коми друкувати та ін). Це робиться за допомогою так званого рядка форматування, який вказує, як і де виводити інші аргументи.

Синтаксис команди такий самий як у аналогічної команди у мові С. Сторінки довідки awk містять детальні пояснення з цього приводу.

Підсумок

Утиліта gawk інтерпретує особливу мову програмування, виконуючи прості перетворення та зміни форматування даних за допомогою кількох рядків коду. Вона є вільною версією популярної у світі UNIX команди awk.

Цей інструмент рядок за рядком зчитує вхідний потік і легко розпізнає стовпцеві дані. Для фільтрування та форматування визначених полів найчастіше використовується команда print.

Декларація змінних на льоту дозволяє легко обчислювати суми, проводити статистичні та інші операції над вхідним потоком даних. Змінні та команди можуть бути записані у сценарій awk для фонового виконання.

Вправи

Наведемо кілька прикладів, де доцільно вживати awk.

  1. В першому прикладі ваш ввід має наступний вигляд:

     Користувач:Ім'я:Прізвище:Номер телефону
    

    Напишіть сценарій awk, який перетворить такий рядок у запис LDAP такого формату:

     dn: uid=Коритсувач, dc=приклад, dc=com
     cn: Ім'я Прізвище
     sn: Прізвище
     telephoneNumber: Номер телефону
    

    Створіть файл з кількома рядками та перевірте роботу сценарію.

  2. Напишіть сценарій bash, вживаючи awk та стандартні команди UNIX, який би показував трьох користувачів з найбільшим об'ємом домашніх каталогів. Спершу виконайте ці команди з командного рядка, а потім запишіть їх у сценарій. Сценарій повинен виводити інформацію у зручному для сприймання вигляді. Якщо все працюватиме як слід, заставте сценарій відправляти результати роботи вам електронною поштою за допомогою, наприклад, ось такої команди:

     mail -s Використання дискового простору <you@your_comp> < result.
    
  3. Створіть XML-подібний вивід зі списку, розділеного табуляторами, наступного змісту:

     Значення  дуже довгого рядка з детальним описом
     Значення  іншого довгого рядка
     Значення  ще одного, ще довшого рядка
     Значення  доооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооовгого рядка, я маю на увазі справді доооооооооооооооооооооооооооооооооооооооооооооооооооовгого.
    

    Вивід має бути ось таким:

     <row>
     <entry>Значення</entry>
     <entry>
     дуже довгого рядка з детальним описом
     </entry>
     </row>
     <row>
     <entry>Значення</entry>
     <entry>
     іншого довгого рядка
     </entry>
     </row>
     <row>
     <entry>Значення</entry>
     <entry>
     ще одного, ще довшого рядка
     </entry>
     </row>
     <row>
     <entry>Значення</entry>
     <entry>
     доооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооооовгого рядка, я маю на увазі справді доооооооооооооооооооооооооооооооооооооооооооооооооооовгого.
     </entry>
     </row>
    

    Додатково, якщо ви знайомі з XML, напишіть сценарій з BEGIN та END, щоб отримати закінчену таблицю. Або зробіть це на мові HTML.