BASH: використовуємо інтерпретатор awk. Відбір рядків із заданого діапазону. Вирази та блоки

Введення у чудову мову з дивним ім'ям

Даніель Роббінс, президент/виконавчий директор, Gentoo Technologies, Inc.

Опис: Awk – чудова мова з дуже дивним ім'ям. У цій першій статті серії, що складається з трьох частин, Даніель Роббінс дає короткий вступоснови програмування на awk. У наступних статтях серії будуть розглянуті більш просунуті теми, і на завершення буде створено серйозний демонстраційний додаток на awk із реальної практики.

Теги цієї статті: awk

Помітити це!

Дата: 29.01.2009

Рівень складності: простий

Коментарі: 0 (Подивитися | Додати коментар - Увійти)

Оцінити цю статтю

На захист awk

У цій серії статей я збираюся зробити з читача майстерного програміста на awk. Я згоден, що у awk не найприємніше і модне ім'я, а GNU-версія awk, названа gawk, звучить відверто дивно. Незнайомі з цією мовою програмісти, почувши його назву, можливо, уявять собі мішанину стародавнього та застарілого коду, здатного довести до запаморочення навіть самого знаючого фахівцяпо UNIX (змусивши його вигукувати "kill -9!" і безперервно бігати за кавою).

Так, у awk аж ніяк не чудове ім'я. Але це чудова мова. Awk створений для обробки тексту та створення звітів, але має багато добре пророблених функцій, що дають можливість серйозного програмування. При цьому, на відміну від деяких інших мов, синтаксис awk звичний і запозичує найкращу з таких мов як C, python і bash (хоча формально awk був створений до python і bash). Awk – одна з тих мов, які, будучи один раз вивчені, стають ключовою частиною стратегічного арсеналу програміста.

Перший крок у awk

Почнемо і спробуємо поекспериментувати з awk, щоб побачити, як він працює. У командному рядку введемо наступну команду: $ awk "(print)" /etc/passwd

В результаті має бути показаний вміст /etc/passwd. Тепер – пояснення того, що робив awk. Викликаючи awk, ми вказали /etc/passwd як вхідний файл. Коли ми запустили awk, він обробив команду print для кожного рядка /etc/passwd по порядку. Весь висновок надіслано в stdout, і ми отримали результат, ідентичний результату команди cat/etc/passwd. Тепер пояснимо блок (print). В awk фігурні дужкивикористовуються для групування блоків тексту, як і C. У нашому блоці тексту є лише одна команда print. У awk команда print без додаткових параметрівдрукує весь вміст поточного рядка.

Ось ще один приклад програми на awk, яка робить те саме: $ awk "(print $0)" /etc/passwd

У awk змінна $0 представляє весь поточний рядок, тому print і print $0 роблять у точності те саме. Якщо завгодно, можна створити програму на awk, яка виводитиме дані, зовсім не пов'язані з вхідними даними. Ось приклад: $ awk "(print "")" /etc/passwd

При передачі рядка "" команді print вона завжди друкує порожній рядок. Якщо протестувати цей скрипт, виявиться, що awk виводить один порожній рядок на кожен рядок файлу /etc/passwd. Це знову відбувається тому, що awk виконує скрипт для кожного рядка у вхідному файлі. Ось ще один приклад: $ awk "(print "hiya")" /etc/passwd

Якщо запустити цей скрипт, він заповнить екран словами "ура". :)

Множинні поля

Awk добре підходить для обробки тексту, розбитого на велику кількість логічних полів, і дає можливість без зусиль звертатися до кожного окремого поля з awk-скрипту. Наступний скрипт роздрукує список усіх облікових записів у системі: $/etc/passwd

У виклику awk у наведеному вище прикладі параметр –F задає ":" як роздільник полів. Обробляючи команду print $1, awk виводить перше поле, зустрінуте у кожному рядку вхідного файла. Ось ще один приклад: $ awk -F":" "( print $1 $3 )" /etc/passwd

Ось фрагмент із виведення на екран цього скрипту:halt7

Як бачимо, awk виводить перше та третє поля файлу /etc/passwd, які являють собою відповідно поля імені користувача та uid. При цьому, хоча скрипт і працює, він не досконалий - немає прогалин між двома вихідними полями! Ті, хто звик програмувати в bash або python, можливо очікували, що команда print $1 $3 вставить пробіл між цими двома полями. Однак коли у програмі на awk два рядки опиняються поруч один з одним, awk зчіплює їх без додавання між ними пробілу. Наступна команда вставить пробіл між полями: $ awk -F":" "( print $1 " " $3 )" /etc/passwd

Коли print викликається у такий спосіб, він послідовно з'єднує $1, " " і $3, створюючи легкочитаний висновок на екрані. Звичайно, ми можемо також вставити мітки полів, якщо потрібно: $ awk -F":" "( print "username: " $1 "\t\tuid:" $3" )" /etc/passwd

В результаті отримуємо такий висновок:username: halt uid:7

username: operator uid:11

username: root uid:0

username: shutdown uid:6

username: sync uid:5

username: bin uid:1

Зовнішні скрипти

Передача скриптів до awk у вигляді аргументів командного рядкаможе бути зручною для невеликих однорядкових текстів, але коли справа доходить до складних багаторядкових програм, напевно буде краще скласти скрипт у вигляді зовнішнього файлу. Після цього можна вказати awk цей скриптовий файл за допомогою -f:$awk -f myscript.awk myfile.in

Розміщення скриптів в окремих текстових файлах також дозволяє скористатися додатковими перевагами awk. Наприклад, наступний багаторядковий скрипт робить те саме, що й один із наших попередніх однорядкових - роздруковує перше поле кожного рядка з /etc/passwd: BEGIN (

Різниця між цими двома методами полягає в тому, як ми задаємо роздільник полів. У цьому скрипті роздільник полів вказується всередині самої програми (установкою змінної FS), тоді як у попередньому прикладі FS налаштовується шляхом передачі awk параметра -F":" в командному рядку. Зазвичай найкраще задавати роздільник полів усередині самого скрипту, просто тому, що тоді не потрібно запам'ятовувати ще один аргумент командного рядка. Пізніше у цій статті ми розглянемо змінну FS докладніше.

Блоки BEGIN та END

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

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

Регулярні вирази та блоки

Awk дозволяє використовувати Регулярні виразидля виборчого виконання окремих блоків програми в залежності від того, збігається чи ні регулярний вираз із поточним рядком. Ось приклад скрипта, який виводить ті рядки, які містять символьну послідовність foo:/foo/ ( print )

Звичайно, можна використовувати складніші регулярні вирази. Ось скрипт, який виводитиме лише рядки, що містять число з плаваючою точкою: /+\.*/ ( print )

Вирази та блоки

Є багато інших способів вибірково виконувати блок програми. Ми можемо помістити перед блоком програми будь-який булевий вираз для керування виконанням цього блоку. Awk буде виконувати блок програми, тільки якщо попередній булевий вираз дорівнює true. Наступний приклад скрипту виводитиме третє поле всіх рядків, у яких перше поле дорівнює fred. Якщо перше поле поточного рядка не дорівнює fred, awk продовжить обробку файлу і не виконає оператор print для поточного рядка: $1 == "fred" ( print $3 )

Awk пропонує повний набіроператорів порівняння, у тому числі звичайні "==", "<", ">", "<=", ">=" і "!=". Крім того, awk надає оператори "~" і "!~", які означають "збігається" і "не збігається". Ось приклад, де виводиться лише третє поле рядка, якщо п'яте поле того ж рядка містить символьну послідовність root:$5 ~ /root/ ( print $3 )

Умовні оператори

Awk також надає дуже приємні C-подібні оператори if. За бажанням можна переписати попередній скрипт з використанням if:(

if ($5 ~ /root/) (

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

Ось більше складний прикладоператора if в awk. Як можна бачити, навіть у разі складних вкладених умовних виразів оператори if виглядають ідентично їх аналогам C:(

if ($1 == "foo") (

if ($2 == "foo") (

) else if ($1 == "bar") (

Використовуючи оператори if, ми можемо конвертувати цей код: ! /matchme/ ( print $1 $3 $4 )

у такій: (

if ($0 !~ /matchme/) (

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

Awk також дає можливість використовувати булеві оператори "||" ("логічне АБО") і "&&"("логічне І"), що дозволяє створювати більш складні булеви вирази: ($1 == "foo") && ($2 == "bar") ( print )

Цей приклад виведе лише ті рядки, в яких перше поле дорівнює foo і друге поле дорівнює bar.

Числові змінні!

Досі ми роздруковували або рядкові змінні, або рядки, або конкретні поля. Однак awk також дає можливість виконувати порівняння як цілих чисел, так і чисел з плаваючою комою. Використовуючи математичні висловлювання, дуже легко написати скрипт, який вважає число порожніх рядківу файлі. Ось такий скрипт: BEGIN ( x=0 )

END ( print "Знайдено " x " порожніх рядків. :)"

У блоці BEGIN ми ініціалізуємо нашу цілісну змінну x значенням нуль. Потім кожен раз, коли awk зустрічає порожній рядок, він виконуватиме оператор x=x+1, збільшуючи xна 1. Після того, як усі рядки будуть оброблені, буде виконано блок END, і awk виведе кінцевий результат, вказавши кількість знайдених порожніх рядків.

Рядкові змінні

Однією з приємних особливостей змінних awk є те, що вони "прості та малі." Я називаю змінні awk "рядковими", тому що всі змінні awk усередині зберігаються як рядки. У той же час змінні awk "прості", тому що зі змінною можна проводити математичні операції, і якщо вона містить правильну числовий рядок, awk автоматично піклується про перетворення рядка на число. Щоб зрозуміти, що я маю на увазі, поглянемо на цей приклад: x="1.01"

# Ми зробили так, що x містить * рядок * "1.01"

# Ми тільки що додали 1 до *рядку*

# Це, до речі, коментар:)

Awk виведе:2.01

Цікаво! Хоча ми надали змінній x рядкове значення 1.01, ми все ж таки змогли додати до неї одиницю. Нам не вдалося б зробити це в bash або python. Насамперед, bash не підтримує арифметику з плаваючою комою. І, хоча в bash є "малі" змінні, вони не є "простими"; для виконання будь-яких математичних операцій bash вимагає, щоб ми уклали наші обчислення у потворні конструкції $() . Якби ми використовували python, нам потрібно було явно перетворити наш рядок 1.01 на значення з плаваючою комою, перш ніж виконувати будь-які розрахунки з нею. Хоч це і не важко, але це все-таки додатковий крок. У випадку awk все це робиться автоматично, і це робить наш код красивим і чистим. Якби нам потрібно було звести перше поле кожного вхідного рядка в квадрат і додати до нього одиницю, ми скористалися б таким скриптом:( print ($1^2)+1 )

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

Безліч операторів

Ще одна приємна особливість awk – це повний комплектматематичні оператори. Крім стандартних складання, віднімання, множення та поділу, awk дає нам можливість використовувати раніше продемонстрований оператор показника ступеня "^", оператор залишку цілісного поділу"%" та безліч інших зручних операторів присвоювання, запозичених з C.

До них відносяться перед-і постінкрементні/декрементні оператори присвоєння (i++, --foo), оператори присвоєння зі складанням/відніманням/множенням/поділом (a+=3, b*=2, c/=2.2, d-=6.2). Але це ще не все - ми маємо також зручні оператори присвоєння з обчисленням залишку цілого чисельного поділу та зведенням у ступінь (a^=2, b%=4).

Розділювачі полів

У awk є власний комплект спеціальних змінних. Деякі з них дають можливість тонкого налаштуванняроботи awk, а інші містять цінну інформаціюпро введення. Ми вже торкнулися однієї з цих спеціальних змінних, FS. Як згадувалося раніше, ця змінна дозволяє задати послідовність символів, яку awk вважатиме розділювачем полів. Коли ми використовували як введення /etc/passwd, FS була встановлена ​​в ":". Це виявилося достатньо, але FS надає нам ще більшу гнучкість.

Значення змінної FS має бути одним символом; їй може бути надано регулярне вираз, що задає символьний шаблон будь-якої довжини. Якщо проводиться обробка полів, розділених одним або декількома символами табуляції, FS потрібно налаштувати таким чином: FS="\t+"

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

Якщо поля розділені порожньою областю (один або кілька пробілів або символів табуляції), можливо, вам захочеться встановити для FS такий регулярний вираз:FS="[[:space:]+]"

Хоча таке налаштування спрацює, у ньому немає потреби. Чому? Тому що за замовчуванням значення FS дорівнює одному символу пробілу, який awk інтерпретує як один або кілька пробілів або символів табуляції. В нашому конкретному прикладізначення FS за промовчанням саме таке, як нам потрібно!

Зі складними регулярними висловлюваннями також не виникає проблем. Навіть якщо записи розділені словом "foo", за яким йдуть три цифри, наступне регулярне вираз дозволить правильно розібрати дані: FS="foo"

Число полів

Наступні дві змінні, які ми збираємося розглянути, зазвичай не призначені для запису в них, а використовуються для читання та отримання корисної інформаціїпро введення. Перша їх - змінна NF, звана також " число полів " . Awk автоматично встановлює значення цієї змінної рівним числу полів у поточному записі. Можна використовувати змінну NF для відображення лише певних вхідних рядків: NF == 3 ( print "у цьому записі три поля: " $0 )

Звичайно, змінну NF можна використовувати і в умовних операторівнаприклад:(

if (NF > 2) (

print $1 " " $2 ":" $3

Номер запису

Ще одна зручна змінна – номер запису (NR). Вона завжди містить номер поточного запису (awk вважає перший запис записом номер 1). Досі ми мали справу з вхідними файлами, що містили один запис на рядок. У таких ситуаціях NR також повідомить номер поточного рядка. Однак, коли ми почнемо обробляти багаторядкові записи в наступних статтях цієї серії, це вже буде не так, тому потрібно виявляти обережність! NR можна використовувати подібно до змінної NF для виведення тільки певних рядківвведення: (NR< 10) || (NR >100) ( print "Ми на записі номер 1-9 або 101 і більше")

Ще один приклад: (

if (NR > 10) (

print "ось тепер пішла справжня інформація!"

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

04.10.2015
16:55

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

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

Якщо awk -програма досить проста і коротка, її код можна набрати прямо в консолі:

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

Як вхідні дані для awk можна використовувати не тільки текстові файли, але й висновок у стандартний потікінших додатків:

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

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

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

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

#include #include #include 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 #include #include

Відбір рядків, що не відповідають регулярному виразу

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

Awk "! /^[/](2).*/" test.cpp

Ось що залишилося:

#include #include #include 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 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() (

Awk "NF == 2" test.cpp

І відповідний висновок:

#include #include #include void test1(); int test2(); return 0;

Реклама

Робота з рядками в awk

Як ви могли переконатися, awk має непоганий набір функцій для фільтрації рядків тексту. Однак, для цих рядків ще можна виконувати різні перетворення. Команди для роботи з рядками повинні бути загорнуті у фігурні дужки (...). Код у дужках послідовно викликається для кожного рядка тексту, що обробляється.

Форматований висновок

Awk є прямий аналог функції printf() мови C . Як приклад виведемо на початку кожного рядка її номер:

Awk "( printf "%-2d %s\n", NR, $0 )" test.cpp

Ось що отримали:

1 #include 2 #include 3 #include 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 #INCLUDE #INCLUDE 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-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

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

#include #include #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 root 3 root 5 root 7 root 8 root 9 root 10

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

Шукаємо рядок із параметром bind-addressу конфігураційному файлі.

root@debian:~# awk ‘/bind-address/’ /etc/mysql/my.cnf
bind-address = 127.0.0.1
bind-address = 192.168.1.110

Пояснення: AWK має наступний синтаксис та опції.

awk[-f файл_програми | ‘програма’] [-Fрозділювач]
[-v змінна = значення] [файл …]

−F value - визначає роздільник (встановлює значення вбудованої змінно FS);
−f file — текст програми зчитується з файлу замість командної лінії. Підтримується читання з множини файлів;
−v var = value - Присвоєння змінної необхідного значення;
−− - Позначає закінчення списку опцій.

Приклад №2

У прикладі вище пошук здійснюється у файлі, але AWK може приймати висновок іншої команди. Спробуймо відповідним чином ускладнити наш приклад.

root@debian-wordpress:~# cat /etc/mysql/my.cnf | awk ‘/bind-address/’
bind-address = 127.0.0.1
bind-address = 192.168.1.110

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

Приклад №3

Вивести список символьних посилань та шляхів до кінцевих файлів.

root@debian:~# ls -l /bin/| awk ‘/lrwxrwxrwx/ ( print $9, $10, $11)’
bzcmp -> bzdiff
bzegrep -> bzgrep
bzfgrep -> bzgrep
bzless -> bzmore
lessfile -> lesspipe
lsmod -> kmod
mt -> /etc/alternatives/mt
nc -> /etc/alternatives/nc
netcat -> /etc/alternatives/netcat
open -> openvt
pidof -> /sbin/killall5
rbash -> bash
rnano -> nano
sh -> dash
sh.distrib -> dash

Пояснення: програма awk являє собою пару шаблонів ( pattern) та дії ( (Action)), а також визначень функцій користувача. Шаблон та дія мають такий вигляд: шаблон (дія) Шаблон чи вплив може бути опущено. У першому випадку дія виконуватиметься до кожного рядка, у другому здійснюватиметься звичайний висновок на екран, еквівалентний команді(print). Ці ключові слова не можна поєднувати з іншими шаблонами.

Вхідний рядок зазвичай складається з полів, розділених символами пробілів. (Цю установку за замовчуванням можна змінити за допомогою вбудованої змінної FSабо опції -F роздільник.) Поля позначаються $1, $2, …; $0 посилається на весь рядок загалом.

Приклад №4

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

root@debian:~# awk -F «:» '( print $1 )' /etc/passwd
root
daemon
bin
sys
sync
games
man

(Виведення команди скорочено)

Пояснення: оскільки у файлі /etc/passwdзаписи зберігаються у вигляді « root:x:0:0:root:/root:/bin/bash«, цілком логічно роздільником вибрати двокрапку і вивести перше поле ( $1 ) кожного рядка ( $0 ).

Приклад №5

Все в тому ж файлі з користувачами можна порахувати їхню кількість.

root@debian:~# awk ‘END (print NR)’ /etc/passwd
25

Пояснення: Спеціальні шаблони BEGINі ENDможна використовувати для отримання керування перед читанням першого вхідного рядка та після прочитання останнього вхідного рядка, відповідно.

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

На відміну від sed, awk може пам'ятати контекст, робити порівняння та багато інших речей, які можуть робити інші мови програмування. Наприклад, вона не обмежена одиничним рядком. При належній вправності, вона може об'єднувати безліч рядків.

Найпростіша форма awk виглядає так:

Awk "( тут_яка-то_дія )"

"Тут_яка_дія" може бути просто виразом для друку результату або чимось набагато складнішим. Синтаксис нагадує мову програмування " C " . Простий приклад:

Awk "(print $1,$3)"

означає надрукувати перший і третій стовпець, де під стовпцями розуміються «речі, розділені білим пробілом». Білий пропуск = табуляція або пропуск.

Живий приклад:

Echo "1 2 3 4" | awk "(print $1,$3)" 1 3

Що може робити AWK?

Головна мета в житті AWK – це маніпулювати її введенням на рядковій основі. Програма awk зазвичай працює у стилі

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

Звичайний синтаксис, що використовується в програмуванні awk, можна описати так:

Awk зразок (команда(и))

Це означає, що

«Подивитися на кожен рядок введення, чи там немає ЗРАЗКА. Якщо він там є, запустити те, що між ()»

Можна пропустити або ЗРАЗОК або КОМАНДУ

Якщо не вказати зразок, то команда буде застосовуватись до КОЖНОГО рядка.

Якщо пропущено команду, то це еквівалентно вказівці (просто надрукувати рядок):

(print)

Конкретні приклади:

Awk "/#/ (print "У цьому рядку є коментар")" /etc/hosts

буде друкувати «У цьому рядку є коментар» для кожного рядка, який містить хоча б один "#" у будь-якому місці рядка в /etc/hosts

Модифікація для наочності

Awk "/#/ (print $0 ":\tУ цьому рядку є коментар")" /etc/hosts

Елемент "//" у зразку - це один із способів задати збіг. Є також інші способи задати, чи рядок збігається. Наприклад,

Awk "$1 == "#" (print "рядок починається з хеша")" /etc/hosts

відповідатиме рядкам, перший стовпець у яких є одиничним "#". Послідовність символів "==" означає точне збіг всього першого стовпця.

Модифікація для наочності:

Awk "$1 == "#" (print $0 "рядок починається з хеша") /etc/hosts

З іншого боку, якщо ви хочете часткового збігу конкретного стовпця, використовуйте оператор "~"

Awk "$1 ~ /#/ (print "ДЕ-ТО в стовпці 1 є хеш")" /etc/hosts

ПАМ'ЯТАЄТЕ, ЩО ПЕРШИЙ СТІЛК МОЖЕ БУТИ ПІСЛЯ БІЛОГО ПРОБІЛУ.

Модифікація для наочності:

Awk "$1 ~ /#/ (print $0 "\tДЕ-то в стовпці 1 є хеш")" /etc/hosts

Введення "# comment" буде відповідати

Введення "# comment" буде ТАКОЖ відповідати

Якщо вам потрібний конкретний збіг «рядок, який починається точно з # і пробілу», ви повинні використовувати

Awk "/^# / (роби щось)"

Множинний збіг

Awk обробить ВСІ ЗРАЗКИ, які відповідають поточному рядку. Тому якщо використати наступний приклад,

Awk " /#/ (print "Є коментар") $1 == "#" (print "Коментар у першому стовпці") /^# / (print "Коментар на самому початку") " /etc/hosts

ТРИ записи буде виведено для рядка на кшталт наступного:

# This is a comment

ДВІ записи для

# This is an indented comment

і тільки одна для

1.2.3.4 hostname # a final comment

Відстеження контексту

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

Тут швидкий приклад, який друкує рядки "ADDR", якщо ви не в секції "secret"

Awk " /secretstart/ ( secret=1) /ADDR/ ( if(secret==0) print $0 ) /* $0 - це повний рядок */ /secretend/ ( secret=0) "

Наступне надрукує вміст, який містить усередині "ADDR" крім випадків, коли було побачено рядок "secretstart". ПОРЯДОК МАЄ ЗНАЧЕННЯ. Наприклад, якщо записати так:

Awk " /ADDR/ ( if(secret==0) print $0 ) /* $0 - це повний рядок */ /secretstart/ ( secret=1) /secretend/ ( secret=0) "

і дати наступне введення

ADDR a normal addr secretstart ADDR secret addr ADDR another secret addr third secret ADDR secretend ADDR normal too

то буде надруковано перший "secret" addr. При тому, що початковий приклад приховає обидва секрети.

Частина третя: Спеціальні змінні

Ми вже сказали про звичайний синтаксис awk. Зараз почнемо розглядати модні штуки.

awk має «спеціальні» рядки відповідності: " BEGIN"і" END"

Директива BEGINвикликається одного разу перед читанням будь-яких рядків з даних ніколи знову.

Директива ENDвикликається після прочитання всіх рядків. Якщо дано кілька файлів, то вона викликається тільки після завершення останнього файлу.

Зазвичай ви будете використовувати BEGINдля різної ініціалізації, а ENDдля підбиття підсумків або очищення.

BEGIN ( maxerrors=3 ; logfile=/var/log/something ; tmpfile=/tmp/blah) ... ( blah blah blah ) /^header/ ( headercount += 1 ) END ( printf("всього підраховано заголовків=% d\n", headercount);

Цей приклад вважатиме кількість разів, яка зустрічається "header" у файлі введення і надрукує загальну кількість лише після завершення обробки всього файлу.

AWK також має безліч інших спеціальних величин, які можна використовувати в секції ( ). Наприклад,

Print NF

дасть вам загальну кількість колонок (Number of Fields – Кількість полів) у поточному рядку. FILENAMEбуде поточним ім'ям файлу, мається на увазі, що ім'я файлу було передано в awk, а не використана труба.

Ви НЕ МОЖЕТЕ ЗМІНИТИ NFсамостійно.

Аналогічно зі змінною NR, Яка каже, як багато рядків ви обробили. ("Number of Records" - Кількість записів)

Є й інші спеціальні змінні, ви навіть такі, які ви можете змінити в середині програми.

Частина четверта: Прості приклади Awk

Щоб проілюструвати і закріпити сказане, розглянемо кілька конкретних прикладів. Для них нам знадобляться три невеликі текстові файли.

Для наступних прикладів давайте створимо файл field_data.txt з таким вмістом:

Roses є red, Violets are blue, Sugar is sweet, And so are you.

Echo -e "Roses are red,\nViolets are blue,\nSugar is sweet,\nAnd so are you." > field_data.txt

Створимо файл letters.txt наступного змісту

A bb ccc dddd ggg hh i

У командному рядку це можна зробити так:

Echo -e "a\nbb\nccc\ndddd\nggg\nhh\ni" > letters.txt

І, нарешті, створимо файл mail-data з таким вмістом:

Amelia 555-5553 [email protected] F Anthony 555-3412 [email protected] A Becky 555-7685 [email protected] A Bill 555-1675 [email protected] A Broderick 555-0542 [email protected] R Camilla 555-2912 [email protected] R Fabius 555-1234 [email protected] F Julie 555-6699 [email protected] F Martin 555-6480 [email protected] A Samuel 555-3430 [email protected] A Jean-Paul 555-2127 [email protected] R

Це можна зробити в командному рядку так:

Wget https://raw.githubusercontent.com/tdhopper/awk-lessons/master/data/mail-data -O mail-data

Простий патерн (зразок)

Якщо нам потрібні рядки довші, ніж два символи, ми хочемо використовувати дію за замовчуванням ( print), то ми отримуємо:

Awk "length $0 > 2" letters.txt bb ccc dddd ggg hh

$0 - це вбудована змінна, що містить рядок.

Проста функція

Якщо ми не вказуємо зразок, то відповідатиме кожен рядок. Тривіальною дією буде надрукувати кожен рядок:

Awk "(print)" letters.txt a bb ccc dddd ggg hh i

Використовуючи функцію lengthяк наша дія, ми можемо отримати довжину кожного рядка:

Awk "( print length )" letters.txt 1 2 3 4 3 2 1

Ця дія застосовується беззастережно до цілого рядка. Ми також можемо вказати це очевидно:

Awk "( print length $0 )" letters.txt 1a 2bb 3ccc 4dddd 3ggg 2hh 1i

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

Awk "BEGIN (print "HI") (print $0) END (print "BYE!")" letters.txt HI a bb ccc dddd ggg hh i BYE!

Ми можемо мати більше елементівуправління під час друку використовуючи printf.

Awk "BEGIN ( printf "%-10s %s\n", "Name", "Number" \ printf "%-10s %s\n", "----", "------" ) \ ( printf "%-10s %s\n", $1, $2 )" mail-data Name Number ---- ------ Amelia 555-5553 Anthony 555-3412 Becky 555-7685 Bill 555-1675 Broderick 555-0542 Camilla 555-2912 Fabius 555-1234 Julie 555-6699 Martin 555-6480 Samuel 555-3430 Jean-Paul 555-2127

Поєднуємо зразки та функції

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

Ми можемо надрукувати довжину всіх рядків, довшу за 2 символи.

Awk "length($0) > 2 ( print length($0) )" letters.txt 3 4 3

Насправді, ми не повинні обмежувати Awk лише одним патерном! Ми можемо мати довільну кількість зразків, розмежованих крапкою з комою або новим рядком:

Awk "length($0) > 2 ( print "Long: " length($0) ); length($0)< 2 { print "Short: " length($0) }" letters.txt Short: 1 Long: 3 Long: 4 Long: 3 Short: 1

Безліч полів

Awk призначена для простої обробки даних з безліччю полів у ряду. Розділювач полів може бути вказаний ключем -F.

Приклад файлу, де роздільником є ​​пробіл:

Awk "( print )" field_data.txt Roses are red, Violets are blue, Sugar is sweet, And so are you.

Якщо ми вказуємо роздільник полів, ми можемо надрукувати друге поле кожного рядка:

Awk -F " " " ( print $2 )" field_data.txt are are is so

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

Awk -F " " " ( print $4 )" field_data.txt you.

Оскільки за замовчуванням роздільником і так є пробіл, то попередня команда дала б такий самий результат і без використання опції -F.Для більш осмисленого прикладу створимо ще один файл rates.txtз наступним вмістом:

Pilcrow,Humphrey,3 Pilcrow,Zora,1 Plinius,Oldone,4 Razniecki,Anton,7 Russell,Bertrand,0

Тепер як роздільник вкажемо , (ком) і виведемо вміст другого стовпця:

Awk -F "," "( print $2 )" rates.txt Humphrey Zora Oldone Anton Bertrand

Вираз розділювача інтерпретується як регулярне вираження.

Awk -F "((so)?are|is) " "(print "Field 1: " $1 "\nField 2: " $2)" field_data.txt Field 1: Roses Field 2: red, Field 1: Violets Field 2 : blue, Field 1: Sugar Field 2: sweet, Field 1: And Field 2: you.

Регулярні вирази

Зразки (патерни) можуть бути регулярними виразами, а не лише вбудованими функціями.

Ми можемо використовувати регулярні вирази для пошуку всіх слів у світі Unix з 5 голосними поспіль.

Awk "/(5)/" /usr/share/dict/words cadiueio Chaouia euouae Guauaenok

Передача змінних у програму

Опція -vдля Awk дозволяє нам передати змінні у програму. Наприклад, ми можемо використовувати це для жорстких констант коду.

Awk -v pi=3.1415 "BEGIN (print pi)" 3.1415

Ми також можемо використовувати -vдля передачі змінних Bash як змінних Awk

Awk -v user=$USER "BEGIN ( print user )" mial

Вирази If-else

If-elseвирази в Awk мають вигляд:

If (умова) тіло-тоді

Наприклад:

Printf "1\n2\n3\n4" | awk \ "( \ if ($1 % 2 == 0) print $1, "is even"; \ else print $1, "is odd" \ )" 1 is odd 2 is even 3 is odd 4 is even

Цикли

Awk включає кілька виразів циклу: while, do whileі for.

Вони мають очікуваний синтаксис C.

Awk \"BEGIN(\i = 0; \while(i< 5) { print i; i+=1; } \ }" 0 1 2 3 4 awk \ "BEGIN { \ i = 0; \ do { print i; i+=1; } while(i < 0) \ }" 0 awk \ "BEGIN { \ i = 0; \ for(i = 0; i<5; i++) print i \ }" 0 1 2 3 4

forтакож може задавати цикл через ключі масиву, whichбуде розглянуто пізніше.

Частина п'ята: Виклик функцій

Наступний компонент AWK - це його спеціальні вбудовані функції.

AWK має функції, які зроблять середнього програміста C дуже щасливим. Тут таке добро як sin()/cos()/tan(), rand(),index(), sprintf(), tolower(), system()

Функції згруповані, їх можна розглядати так:

Математичні

+, -, /, *, sin(), cos(), tan(), atan(), sqrt(), rand(), srand()

Вони самі за себе говорять, принаймні мені хочеться так думати.

Awk -v pi = 3.1415 "BEGIN (print exp (1), log (exp (1)), sqrt (2), sin (pi), cos (pi), atan2 (pi, 2))" 2.71828 1 1.41421 9.26536 e-05-1 1.00387

Програма може згенерувати довільне число в діапазоні (0, 1).

За замовчуванням Awk починає з того самого початку (сида) для Awk. Запуск цієї команди двічі поспіль поверне однаковий результат:

Awk "BEGIN ( print rand(); print rand() )" 0.237788 0.291066

Для встановлення початку (сида) можна використовувати функцію srand:

Awk "BEGIN ( srand(10); print rand(); print rand() )" 0.255219 0.898883 awk "BEGIN ( srand(10); print rand() ; print rand() )" 0.255218 0.

Функція intповертає "найближче ціле число до x, розташоване між x і нулем і з відкинутим провідним нулем".

Awk "BEGIN ( print "int(0.9) = " int(0.9); print " int(-0.9) = " int(-0.9) )" int(0.9) = 0 int(-0.9) = 0

Маніпуляція рядками

  • index()скаже вам, чи зустрічається, а якщо так, то де, рядок у підрядку.
  • match()схожа, але працює для регулярних виразів.
  • sprintf()дає вам способи форматувати висновок і шляхом робити перетворення. Це має бути знайоме всім, хто використовував printf() з C. Наприклад,
newstring=sprintf("one is a number %d, two is a string %s\n", one, two); print newstring

"%d"каже "надрукуй значення, відповідне мені, у вигляді десяткового числа"
"%s"каже "надрукуй значення, відповідне мені, у вигляді рядка"

Тобто. якщо ви хочете об'єднати два рядки без розривів, ОДИН із способів буде використання

Newstring=sprintf("%s%s", один, два)

  • length()просто дає вам простий спосіб підрахувати кількість символів у рядку, якщо вам це знадобиться.

Функція substr(s, m, n)поверне підрядок у n-символів, що починається з позиції m, відрахована від 1.

Awk "( print $1, substr($1, 2, 3) )" field_data.txt Roses ose Violets iol Sugar

index(s, t)повертає `позицію у sв якому зустрічається рядок t, або 0 якщо вона не зустрічається.

Паттерн для index не є регулярним виразом.

Awk "( print $1, index($1, "s") )" field_data.txt Roses 3 Violets 7 Sugar 0 And 0

match(s, r)повертає позицію в sв якій зустрічається регулярний вираз r, або 0, якщо воно не зустрічається. Змінні RSTARTі RLENGTHвстановлюються в позицію і довжину рядка, що збігається.

match- це як indexкрім того, що патерн є регулярним виразом.

Awk "( print $1, match($1, "") )" field_data.txt Roses 3 Violets 7 Sugar 1 And 0 # "Пошук трьох або більше повторюваних букв" awk "( match($1, "(3)"); print $1, "\tpattern start:", RSTART, "\tpattern end:", RLENGTH )" letters.txt a pattern start: 0 pattern end: -1 bb pattern start: 0 pattern end: -1 ccc pattern start: 1 pattern end: 3 dddd pattern start: 1 pattern end: 3 ggg pattern start: 1 pattern end: 3 hh pattern start: 0 pattern end: -1 i pattern start: 0 pattern end: -1

split(s, a, fs)розщеплює рядок на масив елементів a, a, …, a, і повертає n.

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

Awk "BEGIN ( print split("It-was_the-best_of-times", output_array, "[-_]"), output_array, output_array )" 6 was best

sub(r, t, s)замінює на tперше входження регулярного виразу rв рядку s. Якщо не дано s, то використовується $0

sє рядком, у якому відбувається заміна. Замість повернення нового рядка з проведеною заміною буде повернуто кількість зроблених замін (0 або 1).

Awk "BEGIN ( s = "It was the best of times, it was the worst of times"; \ print "Num. matches replaced:", sub("times", "gifs", s); \ print s )" Num. matches replaced: 1 It was the best of gifs, it was the worst of times

gsubробить те саме, що і subза винятком того, що замінюються всі входження регулярного виразу; subі gsubповертають кількість замін.

Awk "BEGIN ( s = "It was the best of times, it was the worst of times"; \ print "Num. matches replaced:", gsub("times", "cats", s); \ print s )" Num. Matches replaced: 2 It was the best of cats, it was the worst of cats sprintf sprintf(fmt, expr, ...) returns the string resulting from formatting expr ... according to the printf(3) format fmt awk "BEGIN (x = sprintf("[%8.3f]", 3.141592654); print x)" [3.142]

Функції рівня системи

system()дозволяє вам викликати потенційно БУДЬ-ЯКИЙ можливий файл в системі. Цільова програма може бути у вашій $PATH, або ви можете вказати її абсолютним шляхом.

Наприклад, страшне

System("rm -rf $HOME");

System("/bin/kill 1")

Якщо ви хочете робити більш складні речі, ви, ймовірно, зрештою робить щось на зразок

Sysstring=sprintf("somecommand %s %s", arg1, arg2); system(sysstring)

close()- це важлива функція, яку часто упускають із виду. Ймовірно, це через те, що немає очевидного виклику open()тому народ не думає про виклик close(). І для більшості цілей це не потрібно. Але ви ПОВИННІ РОБИТИ ЦЕ, якщо ви маєте справу з більш ніж одним файлом виведення.

Awk дає можливість відкрити довільний файл на лету. Наприклад

/^file/ ( print $3 >> $2 )

повинен взяти рядок "file output here-is-a-word", відкрити файл "output" та надрукувати "here-is-a-word" до нього.

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

/^file/ ( if ($2 != oldfile) ( close(oldfile) ); print $3 >> $2 ; oldfile = $2; )

Частина шоста: Масиви

Поняття масиву

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

Якщо вам потрібно мати три значення, ви можете сказати:

Value1="one"; value2="two"; value3="three";

АБО, ви можете використовувати

Values="one"; values="two"; values="three";

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

При використанні змінної як масиву, завжди потрібно поміщати значення квадратних дужок . Ви можете вибрати будь-яке ім'я для змінної масиву, але з цього моменту це ім'я можна використовувати ТІЛЬКИ для масиву. Ви НЕ МОЖЕТЕ осмислено робити

Values="one"; values="newvalue";

Тим не менш, ви МОЖЕТЕ перепризначити величини, як для нормальних змінних. Тобто. наступне Є правильним:

Values="1"; print values; values="one"; print values;

Цікаво, що на відміну від деяких інших мов, ви не зобов'язані використовувати тільки номери. У прикладах вище, насправді тлумачаться як [“1”], [“2”], [“3”]. Що означає, що ви також можете використовувати інші рядки як ідентифікатори і розглядати масив майже як базу даних з однією колонкою. Офіційна назва для цього є «асоційованим масивом».

Numbers["one"]=1; numbers["two"]=2; print numbers["one"]; value="two"; print numbers; value=$1; if(numbers = ""){ print "no such number"; } !}

Коли та як використовувати масиви

Можуть бути різні випадки, коли ви можете вибрати використання масивів. Деякі під час роботи з awk взагалі обходяться без масивів. Але це не зовсім правильна позиція: для масивів існують спеціальні змінні, які, наприклад, показують його розмір (кількість значень у масиві), є зручні для перебору членів масиву конструкції, деякі функції повертають значення у вигляді масиву. У будь-якому випадку, давайте розглянемо кілька прикладів, які можуть стати в нагоді.

Збереження інформації для подальшого використання

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

/special/( savedwords=$2; lnum+=1; ) END ( count=0; while(savedwords != "") ( print count,savedwords; count+=1; ) )

Замість простого виведення слів, ви можете використовувати секцію END, щоб перед їх відображенням зробити додаткову обробку, яка вам може бути потрібна.

Якщо ви хочете присвоїти унікальний індекс значенням (для уникнення дублікатів), ви взагалі можете відсилати їх значення за їхніми власними рядками. Або, наприклад, зберегти масив з колонкою 3, проіндексований за відповідним значенням колонки 2.

( threecol[$2]=$3 ) END ( for (v in threecol) ( print v, threecol[v] ) )

Масиви та функція split()

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

Це є variable:field:type line There can be multiple:type:values ​​here

У прикладі вище четверте відокремлене пробілом поле має підполя, розділені двокрапками. Тепер, скажімо, ви хочете дізнатися значення другого підполя в четвертому великому полі. Один із способів зробити це, викликати дві awk пов'язані трубою:

Awk "(print $4)" | awk -F: "(print $2)"

Іншим способом буде на льоту змінити значення "FS", яка містить роздільник полів (судячи з усього, це працює не з усіма реалізаціями awk):

Awk "( newline=$4; fs=FS; FS=":"; $0=newline; print $2 ; FS=fs; )"

Але ви також можете зробити це з масивами, використовуючи функцію split() таким чином:

Awk "( newline=$4; split(newline,subfields,":"); print subfields) "

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

Отже, Awk забезпечує обмежену кількість структур даних. Окрім скалярних та рядкових змінних, у мову вбудована структура масивних даних. Хоча офіційно вона називається «масиви», ця структура насправді є асоційованим масивом, аналогічною структурі даних dict у Python.

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

Awk "BEGIN ( \ a = 1.1; \ a = 0; \ a ["DOG"] = "CAT"; \ print a, a, a ["DOG"] \ )" 1.1 0 CAT

Awk не друкуватиме змінну без індексу:

Awk "BEGIN (\a["DOG"] = "CAT"; \print a\)" awk: cmd. line:3: fatal: attempt to use array 'a' in scalar context

Хоча ми можемо зробити цикл за ключом використовуючи for:

Awk "BEGIN ( \ a = 1.1; \ a = 0; \ a ["DOG"] = "CAT"; \ for(k in a) print(a[k]) \ )" CAT 0 1.1

Частина сьома: AWK та оболонки (sh/ksh/bash/csh)

Іноді функціоналу AWK може бути недостатньо. У цьому випадку можна інтегрувати awk у скрипт оболонки. Далі кілька прикладів, як це можна зробити.

Простий висновок

Іноді хочеться використовувати awk просто як програму форматування, і скидати висновок прямо користувачу Наступний скрипт приймає як аргумент ім'я користувача і використовує awk для дампа інформації про нього з /etc/passwd.

Примітка: зверніть увагу, що в скрипті одинарні лапки розкриваються (а не є вкладеними) і між двома розкритими парами одинарних лапок стоїть змінна $1 (друга), яка в даному випадку є аргументом скрипта, тоді як $1 є частиною синтаксису $1 (означає перше поле у ​​рядку).

#!/bin/sh while [ "$1" != "" ] ; do awk -F: "$1 == ""$1"" ( print $1,$3) " /etc/passwd shift done

Присвоєння змінним оболонки виводу awk

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

І знову, зверніть увагу, як відбувається закриття одинарних лапок у виразі awk. Після закритої (другої) лапки, $1 є змінною, в яку передано значення першого аргументу скрипту, а не частиною синтаксису awk.

#!/bin/sh user="$1" if [ "$user" == "" ] ; then echo ERROR: need a username ; exit; fi usershell=`awk -F: "$1 == ""$1"" ( print $7) " /etc/passwd` grep -l $usershell /etc/shells if [ $? -ne 0]; then echo ERROR: shell $usershell for user $user not in /etc/shells fi

Інші альтернативи:

# Дивіться "man regex" usershell=`awk -F: "/^"$1":/ ( print $7) " /etc/passwd` echo $usershell; # Тільки сучасні awk приймають -v. Вам може знадобитися використовувати "nawk" або "gawk" usershell2=`awk -F: -v user=$1 "$1 == user ( print $7) " /etc/passwd` echo $usershell2;

Пояснення додаткових наведених вище методів залишається домашнім завданням читачеві 🙂

Передача даних в awk трубою

Іноді хочеться помістити awk як фільтр даних, у велику програму або команду в одному рядку, що вводиться в запит оболонки. Приклад такої команди в скрипті (як аргументи скрипта передається список файлів логів веб-сервера, оскільки запис в журнал налаштовується і логи можуть мати різну структуру, для працездатності в конкретних випадках може знадобитися підправити команди):

#!/bin/sh grep -h "/index.html" $* | awk -F" "(print $4)" | sort -u

  1. Цікава стаття, хочу подякувати за працю.

    Знайшов у ній не точність. Якщо виконати рядок із прикладу

    Awk -F " " " ( print $2 )" field_data.txt

    вона виведе те саме, що й

    Awk "( print $2 )" field_data.txt

    Виходить приклад з -Fневдало описаний.