mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
395 lines
20 KiB
Markdown
395 lines
20 KiB
Markdown
|
---
|
|||
|
filename: learnawk-ua.awk
|
|||
|
contributors:
|
|||
|
- ["Marshall Mason", "http://github.com/marshallmason"]
|
|||
|
translators:
|
|||
|
- ["Ihor Sokorchuk", "https://github.com/IhorSokorchuk"]
|
|||
|
---
|
|||
|
|
|||
|
AWK є стандартним інструментом у кожній POSIX-сумісній системі UNIX.
|
|||
|
Він схожий на flex/lex із командного рядка й чудово підходить для завдань
|
|||
|
обробки тексту та інших скриптових задач. Він має подібний до C синтаксис,
|
|||
|
але без обов’язкових крапок з комою (хоча їх все одно потрібно використовувати,
|
|||
|
якщо ви пишете однорядковий код, для якого AWK відмінно підходить),
|
|||
|
у ньому немає ручного керування пам’яттю та статичної типізації.
|
|||
|
Він відмінно обробляє текст. Ви можете викликати його зі сценарію оболонки або
|
|||
|
використовувати як окрему мову сценаріїв.
|
|||
|
|
|||
|
Навіщо використовувати AWK замість Perl? Це читабельність коду.
|
|||
|
Адже AWK легше читати, ніж Perl. Для простих сценаріїв обробки тексту,
|
|||
|
особливо тих, які читають файли рядок за рядком і розбивають їх
|
|||
|
за роздільниками, AWK є чи не найкращим інструментом.
|
|||
|
|
|||
|
```awk
|
|||
|
#!/usr/bin/awk -f
|
|||
|
|
|||
|
# Коментарі у AWK схожі на цей коментар.
|
|||
|
|
|||
|
# Програми AWK складаються з набору шаблонів і дій.
|
|||
|
pattern1 { action; } # зовсім як lex
|
|||
|
pattern2 { action; }
|
|||
|
|
|||
|
# Скрипт виконує неявний цикл, у якому AWK автоматично читає та аналізує кожен
|
|||
|
# запис із кожного наданого йому файла. Кожен такий запис розділяється на поля
|
|||
|
# роздільником FS, який за замовчуванням має значення пробіл (кілька пробілів
|
|||
|
# або символів табуляції вважаються одним роздільником).
|
|||
|
# Можна встановити значення FS в командному рядку (-FC) або у шаблоні BEGIN.
|
|||
|
|
|||
|
# Одним із спеціальних шаблонів є шаблон BEGIN. Цей шаблон є дійсним
|
|||
|
# ДО прочитання будь-якого з вхідних файлів.
|
|||
|
# Ще один спеціальний шаблон END є дійсним після кінця останнього з вказаних
|
|||
|
# вхідних файлів, або після стандартного введення, якщо вхідні файли не вказано.
|
|||
|
# Також використовується роздільник між полями виводу (OFS), значення якого
|
|||
|
# ви також можете призначити, і для якого за замовчуванням встановлено
|
|||
|
# значення один пробіл.
|
|||
|
|
|||
|
|
|||
|
BEGIN {
|
|||
|
|
|||
|
# BEGIN запускатиметься на початку програми. Тут розміщують весь код
|
|||
|
# попереднього налаштування перед обробкою будь-яких текстових файлів.
|
|||
|
# Якщо ваш скрипт не читає текстові файли, розглядайте BEGIN як
|
|||
|
# головну точку входу.
|
|||
|
|
|||
|
# Змінні є глобальними. Просто встановіть їх і використовуйте.
|
|||
|
# Оголошувати змінні не потрібно.
|
|||
|
count = 0;
|
|||
|
|
|||
|
# Оператори є такими ж як у мові C та схожих на неї мовах програмування.
|
|||
|
a = count + 1;
|
|||
|
b = count - 1;
|
|||
|
c = count * 1;
|
|||
|
d = count / 1; # цілочислене ділення
|
|||
|
e = count % 1; # модуль
|
|||
|
f = count ^ 1; # піднесення до степеня
|
|||
|
|
|||
|
a += 1;
|
|||
|
b -= 1;
|
|||
|
c *= 1;
|
|||
|
d /= 1;
|
|||
|
e %= 1;
|
|||
|
f ^= 1;
|
|||
|
|
|||
|
# Постфіксний оператор збільшення та зменшення на одиницю
|
|||
|
a++;
|
|||
|
b--;
|
|||
|
|
|||
|
# Префіксний оператор, який повертає збільшене на одиницю значення
|
|||
|
++a;
|
|||
|
--b;
|
|||
|
|
|||
|
# Зверніть також увагу, що в кінці рядка не обов'язково вказувати
|
|||
|
# розділовий знак крапка з комою.
|
|||
|
|
|||
|
# Оператор галудження
|
|||
|
if (count == 0)
|
|||
|
print "Starting with count of 0";
|
|||
|
else
|
|||
|
print "Huh?";
|
|||
|
|
|||
|
# Можна використовувати тернарний оператор
|
|||
|
print (count == 0) ? "Starting with count of 0" : "Huh?";
|
|||
|
|
|||
|
# Для блоків, що складаються з кількох рядків, використовують дужки
|
|||
|
while (a < 10) {
|
|||
|
print "String concatenation is done" " with a series" " of"
|
|||
|
" space-separated strings";
|
|||
|
print a;
|
|||
|
|
|||
|
a++;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < 10; i++)
|
|||
|
print "Good ol' for loop";
|
|||
|
|
|||
|
# Порівняння є стандартними:
|
|||
|
# a < b # менше ніж
|
|||
|
# a <= b # менше або дорівнює
|
|||
|
# a != b # не дорівнює
|
|||
|
# a == b # дорівнює
|
|||
|
# a > b # більше ніж
|
|||
|
# a >= b # більше або дорівнює
|
|||
|
|
|||
|
# Логічні оператори також стандартні:
|
|||
|
# a && b # AND - І
|
|||
|
# a || b # OR - АБО
|
|||
|
|
|||
|
# Крім того, є перевірка на збіг із регулярним виразом:
|
|||
|
if ("foo" ~ "^fo+$")
|
|||
|
print "Fooey!";
|
|||
|
if ("boo" !~ "^fo+$")
|
|||
|
print "Boo!";
|
|||
|
|
|||
|
# Масиви:
|
|||
|
arr[0] = "foo";
|
|||
|
arr[1] = "bar";
|
|||
|
|
|||
|
# Також можна ініціалізувати масив за допомогою вбудованої функції split():
|
|||
|
n = split("foo:bar:baz", arr, ":");
|
|||
|
|
|||
|
# Підтримуються асоціативні масиви (насправді, всі масиви асоціативні):
|
|||
|
assoc["foo"] = "bar";
|
|||
|
assoc["bar"] = "baz";
|
|||
|
|
|||
|
# Та багатовимірні масиви (з деякими обмеженнями, які описуються далі):
|
|||
|
multidim[0,0] = "foo";
|
|||
|
multidim[0,1] = "bar";
|
|||
|
multidim[1,0] = "baz";
|
|||
|
multidim[1,1] = "boo";
|
|||
|
|
|||
|
# Можна перевірити членство в масиві:
|
|||
|
if ("foo" in assoc)
|
|||
|
print "Fooey!";
|
|||
|
|
|||
|
# Оператор 'in' також можна використовувати для обходу ключів масиву:
|
|||
|
for (key in assoc)
|
|||
|
print assoc[key];
|
|||
|
|
|||
|
# Командний рядок знаходиться у спеціальному масиві з ім'ям ARGV
|
|||
|
for (argnum in ARGV)
|
|||
|
print ARGV[argnum];
|
|||
|
|
|||
|
# Можна видаляти елементи масиву. Це особливо корисно для того, щоб AWK
|
|||
|
# не розглядав аргументи як імена файлів для обробки
|
|||
|
delete ARGV[1];
|
|||
|
|
|||
|
# Кількість аргументів командного рядка міститься у змінній з іменем ARGC
|
|||
|
print ARGC;
|
|||
|
|
|||
|
# AWK має багато вбудованих функцій. Вони діляться на три категорії, які
|
|||
|
# буде розглянуто далі.
|
|||
|
|
|||
|
return_value = arithmetic_functions(a, b, c);
|
|||
|
string_functions();
|
|||
|
io_functions();
|
|||
|
}
|
|||
|
|
|||
|
# Функції декларуються так:
|
|||
|
function arithmetic_functions(a, b, c, d) {
|
|||
|
|
|||
|
# Однією з дратівливих особливостей AWK є те, що в цій мові немає
|
|||
|
# локальних змінних. Усе є глобальним.
|
|||
|
# Для коротких сценаріїв це добре, навіть корисно, але для довших
|
|||
|
# сценаріїв це може бути проблемою.
|
|||
|
|
|||
|
# Проте, є обхідний шлях (huk). Вказані у функції аргументи є локальними
|
|||
|
# у функції. Крім того, AWK дозволяє вказувати більше аргументів функції,
|
|||
|
# ніж потрібно. Тому, просто вставте локальну змінну в оголошення
|
|||
|
# функції, як це зроблено вище. Можна додати також додаткові пробіли,
|
|||
|
# щоб відрізнити фактичні параметри функції від створених у такий спосіб
|
|||
|
# локальних змінних.
|
|||
|
# У наведеному вище прикладі, змінні a, b, c є фактичними параметрами,
|
|||
|
# тоді як d є лише локальною змінною.
|
|||
|
|
|||
|
# Тепер, розглянемо математичні функції
|
|||
|
|
|||
|
# Більшість реалізацій AWK підтримують стандартні тригонометричні функції:
|
|||
|
localvar = sin(a);
|
|||
|
localvar = cos(a);
|
|||
|
localvar = atan2(b, a); # арктангенс b / a
|
|||
|
|
|||
|
# та логаритмічні обчислення:
|
|||
|
localvar = exp(a);
|
|||
|
localvar = log(a);
|
|||
|
|
|||
|
# квадратний корінь:
|
|||
|
localvar = sqrt(a);
|
|||
|
|
|||
|
# округлення до цілого числа:
|
|||
|
localvar = int(5.34); # localvar => 5
|
|||
|
|
|||
|
# випадкові числа:
|
|||
|
srand(); # встановити т.зв. "сіль" (без аргумента використовується час)
|
|||
|
localvar = rand(); # випадкове число від 0 до 1.
|
|||
|
|
|||
|
# Повернути значення можна так:
|
|||
|
return localvar;
|
|||
|
}
|
|||
|
|
|||
|
# Функції для обробки рядків тексту
|
|||
|
function string_functions( localvar, arr) {
|
|||
|
|
|||
|
# AWK, як мова обробки тексту, підтримує функції для обробки рядків.
|
|||
|
# У багатьох з цих функцій використовуються регулярні вирази.
|
|||
|
|
|||
|
# Пошук і заміна, першої (sub) або всіх відповідностей (gsub).
|
|||
|
# Обидві вони повертають кількість замінених відповідностей.
|
|||
|
localvar = "fooooobar";
|
|||
|
sub("fo+", "Meet me at the ", localvar); # localvar => "Meet me at the bar"
|
|||
|
gsub("e", ".", localvar); # localvar => "m..t m. at th. bar"
|
|||
|
|
|||
|
# Пошук підрядка, який збігається з регулярним виразом:
|
|||
|
# index() робить те ж саме, але не використовує регулярний вираз.
|
|||
|
match(localvar, "t"); # => 4, оскільки 't' є четвертим символом
|
|||
|
|
|||
|
# Розбити рядок за роздільником
|
|||
|
n = split("foo-bar-baz", arr, "-"); # a[1] = "foo"; a[2] = "bar"; a[3] = "baz"; n = 3
|
|||
|
|
|||
|
# Інші корисні речі
|
|||
|
sprintf("%s %d %d %d", "Testing", 1, 2, 3); # => "Testing 1 2 3"
|
|||
|
substr("foobar", 2, 3); # => "oob"
|
|||
|
substr("foobar", 4); # => "bar"
|
|||
|
length("foo"); # => 3
|
|||
|
tolower("FOO"); # => "foo"
|
|||
|
toupper("foo"); # => "FOO"
|
|||
|
}
|
|||
|
|
|||
|
# Функції введення-виведення
|
|||
|
function io_functions( localvar) {
|
|||
|
|
|||
|
# Ви вже бачили print
|
|||
|
print "Hello world";
|
|||
|
|
|||
|
# Є також printf
|
|||
|
printf("%s %d %d %d\n", "Testing", 1, 2, 3);
|
|||
|
|
|||
|
# AWK сам по собі не має дескрипторів файлів. Він автоматично відкриває
|
|||
|
# дескриптор файлу, коли виконується дія, яка потребує дискриптора.
|
|||
|
# Рядок, який використано для цього, можна розглядати як дескриптор файлу
|
|||
|
# для введення-виводу. Це схоже на сценарії оболонки.
|
|||
|
# Рядок, при цьому, повинен точно збігатися. Тому використовуйте змінну:
|
|||
|
outfile = "/tmp/foobar.txt";
|
|||
|
print "foobar" > outfile;
|
|||
|
|
|||
|
# Тепер, рядок вихідного файлу є дескриптором файлу.
|
|||
|
# Ви можете закрити його:
|
|||
|
close(outfile);
|
|||
|
|
|||
|
# Ось як запустити команду в оболонці
|
|||
|
system("echo foobar"); # => друкує foobar
|
|||
|
|
|||
|
# Читає рядок із потоку стандартного введення та зберігає в localvar
|
|||
|
getline localvar;
|
|||
|
|
|||
|
# Читає рядок з каналу (використовуйте рядок, щоб правильно його закрити)
|
|||
|
cmd = "echo foobar";
|
|||
|
cmd | getline localvar; # localvar => "foobar"
|
|||
|
close(cmd);
|
|||
|
|
|||
|
# Читає рядок з файлу та зберігає в localvar
|
|||
|
infile = "/tmp/foobar.txt";
|
|||
|
getline localvar < infile;
|
|||
|
close(infile);
|
|||
|
}
|
|||
|
|
|||
|
# Як вже вказувалося, програми AWK складаються з набору шаблонів та дій.
|
|||
|
# Було розглянуто шаблони BEGIN та END . Інші шаблони використовуються тоді,
|
|||
|
# коли скрипт обробляє рядки з файлів або стандартного потоку введення.
|
|||
|
#
|
|||
|
|
|||
|
# Вказані у командному рядку аргументи розглядаються як імена файлів для
|
|||
|
# обробки. Усі ці файли обробляються послідовно один за одним.
|
|||
|
# Виконується неявний цикл, в якому обробляються усі рядки з цих файлів.
|
|||
|
# Шаблони та дії AWK схожі на оператор switch всередині циклу.
|
|||
|
|
|||
|
/^fo+bar$/ {
|
|||
|
# Ця дія буде виконуватися для кожного рядка, який збігається з регулярним
|
|||
|
# виразом: /^fo+bar$/, і буде пропущена для усіх рядків,
|
|||
|
# які не збігаються з ним. Давайте просто надрукуємо рядок:
|
|||
|
|
|||
|
print;
|
|||
|
|
|||
|
# Без аргументів! Це тому, що print має аргумент за замовчуванням: $0.
|
|||
|
# $0 - ім'я змінної, яка містить увесь поточний рядок. Ця змінна
|
|||
|
# створюється автоматично.
|
|||
|
|
|||
|
# Також існують інші змінні зі знаком $. Кожен рядок неявно розбивається
|
|||
|
# перед викликом кожної дії так, як це робить оболонка. І так само, як і
|
|||
|
# в оболонці, до кожного поля можна отримати доступ із відповідної змінної
|
|||
|
# зі знаком долара.
|
|||
|
|
|||
|
# Тут буде виведене друге та четверте поля з рядка
|
|||
|
print $2, $4;
|
|||
|
|
|||
|
# AWK автоматично визначає багато інших змінних, щоб допомогти перевірити
|
|||
|
# та обробити кожен рядок. Однією з них є NF.
|
|||
|
|
|||
|
# Друкує кількість полів у цьому рядку
|
|||
|
print NF;
|
|||
|
|
|||
|
# Друк останнього поля у цьому рядку
|
|||
|
print $NF;
|
|||
|
}
|
|||
|
|
|||
|
# Кожен шаблон є логічним виразом. Регулярний вираз у наведеному шаблоні
|
|||
|
# також є перевіркою на істине/хибне, але частина виразу прихована.
|
|||
|
# Шаблон обробляє кожен вхідний рядок і його повна версія є такою:
|
|||
|
$0 ~ /^fo+bar$/ {
|
|||
|
print "Equivalent to the last pattern";
|
|||
|
}
|
|||
|
|
|||
|
a > 0 {
|
|||
|
# цей блок виконуватиметься для кожного рядка, допоки змінна a буде
|
|||
|
# більшою від 0
|
|||
|
}
|
|||
|
|
|||
|
# Ідея AWK зрозуміла. Обробка текстових файлів, порядкове зчитування і
|
|||
|
# обробка, зокрема розбиття за роздільником, настільки поширені в UNIX,
|
|||
|
# що AWK — це мова сценаріїв, яка виконує все це сама, без ваших вказівок.
|
|||
|
# Все, що вам потрібно зробити, це написати шаблони та дії на основі того,
|
|||
|
# що ви очікуєте від введення, і що ви хочете з ним зробити.
|
|||
|
|
|||
|
# Ось короткий приклад простого сценарію, для якого чудово підходить AWK.
|
|||
|
# Цей сценарій прочитає ім’я зі стандартного потоку введення, а потім надрукує
|
|||
|
# середній вік для людей з цим іменем.
|
|||
|
# Нехай, як аргумент скрипта вказано ім’я файла з такими даними:
|
|||
|
#
|
|||
|
# Bob Jones 32
|
|||
|
# Jane Doe 22
|
|||
|
# Steve Stevens 83
|
|||
|
# Bob Smith 29
|
|||
|
# Bob Barker 72
|
|||
|
#
|
|||
|
|
|||
|
# Ось сценарій:
|
|||
|
|
|||
|
BEGIN {
|
|||
|
|
|||
|
# Спершу попросимо користувача ввести ім'я
|
|||
|
print "What name would you like the average age for?";
|
|||
|
|
|||
|
# Отримати рядок зі стандартного потоку введення, а не з файлів
|
|||
|
# командного рядка
|
|||
|
getline name < "/dev/stdin";
|
|||
|
}
|
|||
|
|
|||
|
# Тепер обробимо кожен рядок, першим полем якого є вказане ім'я
|
|||
|
$1 == name {
|
|||
|
|
|||
|
# Тут усередині ми маємо доступ до низки корисних змінних, які вже
|
|||
|
# встановлені для нас попередньо:
|
|||
|
# $0 - це весь рядок
|
|||
|
# $3 - це третє поле, вік, який нас тут цікавить
|
|||
|
# NF - це кількість полів, яка має бути 3
|
|||
|
# NR - це кількість записів (рядків), переглянутих на поточний момент
|
|||
|
# FILENAME - ім'я файла, який обробляється
|
|||
|
# FS - це роздільник полів, який використовується. Тут це - " "
|
|||
|
# ... є багато інших задокументованих на сторінці керівництва змінних
|
|||
|
|
|||
|
# Обчислимо поточну суму та кількість рядків, які відповідають шаблону
|
|||
|
sum += $3;
|
|||
|
nlines++;
|
|||
|
}
|
|||
|
|
|||
|
# Ще один спеціальний шаблон називається END. Він виконується після обробки
|
|||
|
# всіх текстових файлів. На відміну від BEGIN, він запуститься, якщо були
|
|||
|
# дані для обробки. Він виконуватиметься після того, як будуть прочитані та
|
|||
|
# оброблені, відповідно до вказаних правил та дій, усі файли з вхідними даними.
|
|||
|
# Його мета зазвичай полягає в тому, щоб вивести якийсь остаточний звіт або
|
|||
|
# зробити щось із накопиченою протягом сценарію сукупністю даних
|
|||
|
|
|||
|
END {
|
|||
|
if (nlines)
|
|||
|
print "The average age for " name " is " sum / nlines;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Детальніше дивіться:
|
|||
|
|
|||
|
* [Awk tutorial](http://www.grymoire.com/Unix/Awk.html)
|
|||
|
* [Awk man page](https://linux.die.net/man/1/awk)
|
|||
|
* [The GNU Awk User's Guide](https://www.gnu.org/software/gawk/manual/gawk.html) GNU Awk is found on most Linux systems.
|
|||
|
* [AWK one-liner collection](http://tuxgraphics.org/~guido/scripts/awk-one-liner.html)
|
|||
|
* [Awk alpinelinux wiki](https://wiki.alpinelinux.org/wiki/Awk) a technical summary and list of "gotchas" (places where different implementations may behave in different or unexpected ways).
|
|||
|
* [basic libraries for awk](https://github.com/dubiousjim/awkenough)
|