Кладовочка

О разном, чтобы было под рукой онлайн

Шпаргалка AWK

AWK - мощный язык обработки текста и потоковых данных, особенно удобен для работы с колонками в таблицах (CSV, лог-файлы). Назван по фамилиям авторов: Aho, Weinberger, Kernighan.

Основной синтаксис

# Базовые варианты запуска
awk 'программа' файл              # Простая программа
awk -F':' 'программа' файл        # С разделителем полей
awk -f скрипт.awk файл            # Программа из файла
awk -v var=значение 'программа' файл  # С переменной

# Реальные примеры
awk '{print $1}' /etc/passwd                    # Имена пользователей
awk -F':' '{print $1}' /etc/passwd              # Поля через :
awk -f analyze.awk data.txt                      # Запуск скрипта
awk -v min=100 '$3 > min' sales.txt              # Фильтр с переменной

Структура программы

# Общая структура
BEGIN    { действия до обработки }
условие  { действия для строк, удовлетворяющих условию }
/регулярка/ { действия для строк, совпадающих с regexp }
END      { действия после обработки }

# Реальные примеры
awk 'BEGIN {print "=== НАЧАЛО ОТЧЁТА ==="}
     $3 > 1000 {print $1, $3}
     END {print "=== КОНЕЦ ОТЧЁТА ==="}' data.txt

# Специальные правила
awk '/error/ {print "Ошибка в строке", NR}' log.txt     # Поиск ошибок
awk '$1 == "END" {next} {print}' data.txt                # Пропуск строк с END
awk 'NF < 3 {print "Короткая строка:", $0}' data.txt     # Строки с <3 полями

Специальные переменные

Основные переменные

  • $0 - вся текущая строка

    awk '{print $0}' file.txt        # печать всех строк
  • $1, $2, ..., $NF - поля строки

    awk '{print $1, $3}' data.txt    # 1-е и 3-е поле
    awk '{print $NF}' data.txt       # последнее поле
  • NF - количество полей в строке

    awk '{print NF, $0}' data.txt    # показать число полей
  • NR - номер текущей строки (общий для всех файлов)

    awk 'NR==10' file.txt             # 10-я строка
    awk 'END{print NR}' file.txt      # всего строк
  • FNR - номер строки в текущем файле

    awk 'FNR==1' file1.txt file2.txt  # первые строки каждого файла
  • FILENAME - имя текущего файла

    awk '{print FILENAME, $0}' *.txt  # показать имя файла для каждой строки

Разделители

  • FS - разделитель полей на входе (field separator)

    awk 'BEGIN{FS=","} {print $1}' data.csv
  • OFS - разделитель полей на выходе (output field separator)

    awk 'BEGIN{OFS=";"} {$1=$1; print}' data.txt
  • RS - разделитель записей (record separator)

    awk 'BEGIN{RS=""} {print "Абзац:", $0}' text.txt
  • ORS - разделитель записей на выходе

    awk 'BEGIN{ORS="\n\n"} {print}' file.txt   # двойной интервал

Дополнительные

  • ARGC, ARGV - аргументы командной строки

    awk 'BEGIN{for(i=0;i<ARGC;i++) print ARGV[i]}' a b c
  • IGNORECASE - игнорировать регистр (1 = да, 0 = нет)

    awk 'BEGIN{IGNORECASE=1} /error/' log.txt

Управление потоком

next - пропустить остальные правила

# Пропустить комментарии и пустые строки
awk '/^#/ {next} /^$/ {next} {print "Данные:", $0}' config.txt

exit - завершить выполнение

# Остановиться после нахождения критической ошибки
awk '/FATAL/ {print "Критическая ошибка!"; exit} {print}' log.txt

# exit с кодом возврата
awk 'BEGIN{print "Ошибка" > "/dev/stderr"; exit 1}'

nextfile - перейти к следующему файлу (gawk)

# Обработать только первые 5 строк каждого файла
gawk 'FNR > 5 {nextfile} {print FILENAME ":", $0}' *.txt

Модификация полей

При изменении поля AWK пересобирает $0 с использованием OFS:

# Сделать имена заглавными
awk '{$1 = toupper($1); print}' users.txt

# Удалить второе поле
awk '{$2 = ""; print}' contacts.txt

# Оставить только имя и зарплату
awk '{$0 = $1 " " $3; print}' employees.txt

# Поменять местами поля
awk '{temp = $1; $1 = $2; $2 = temp; print}' data.txt

# Добавить префикс
awk '{$0 = "ID: " $0; print}' data.txt

Работа с файлами

Обработка двух файлов

# Ключевой паттерн: FNR==NR для первого файла
# Добавить отделы к сотрудникам
# employees.txt: 101 Иван 50000
# departments.txt: 101 IT
awk 'FNR==NR {dept[$1]=$2; next}
     $1 in dept {print $0, dept[$1]}' departments.txt employees.txt
# Вывод: 101 Иван 50000 IT

# Найти строки, общие для двух файлов
awk 'NR==FNR {a[$0]; next} $0 in a' file1.txt file2.txt

# Найти строки, уникальные для file1
awk 'NR==FNR {a[$0]; next} !($0 in a)' file2.txt file1.txt

Разделение на файлы

# Разделить по значению поля
awk '{print > $1 ".txt"}' data.txt

# Разделить по условию
awk '{if($3 > 1000) print > "high.txt"; else print > "low.txt"}' sales.txt

Строковые функции

Основные строковые функции

  • length([строка]) - длина строки

    awk '{print length($0)}' file.txt        # длина каждой строки
    awk 'length > 80' file.txt                # строки длиннее 80 символов
  • substr(строка, начало [, длина]) - подстрока

    awk '{print substr($1, 1, 3)}' data.txt   # первые 3 символа
    awk '{print substr($0, length-5)}' file   # последние 5 символов
  • index(строка, подстрока) - позиция подстроки

    awk '{if(index($0, "ERROR") > 0) print}' log.txt
  • split(строка, массив [, разделитель]) - разбить строку

    awk '{split($0, arr, ":"); print arr[1], arr[3]}' /etc/passwd

Замена и регистр

  • gsub(regexp, замена [, строка]) - глобальная замена

    awk '{gsub(/[0-9]/, "X"); print}' file.txt    # цифры → X
    awk '{gsub(/\s+/, " "); print}' file.txt      # сжать пробелы
  • sub(регулярка, замена [, строка]) - замена первого вхождения

    awk '{sub(/^/, "> "); print}' file.txt        # добавить в начало
    awk '{sub(/\s+$/, ""); print}' file.txt       # удалить пробелы в конце
  • toupper(строка) / tolower(строка) - регистр

    awk '{print toupper($1)}' names.txt           # ЗАГЛАВНЫЕ
    awk '{print tolower($1)}' names.txt           # строчные
  • match(строка, регулярка [, массив]) - поиск совпадения

    awk '{if(match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/)) print "IP найден"}' log.txt
  • sprintf(формат, значения) - форматирование

    awk '{sum+=$2} END{print sprintf("Среднее: %.2f", sum/NR)}' data.txt

Математические функции

Базовые

  • int(x) - целая часть

    awk '{print int($1)}' data.txt        # отбросить дробную часть
  • sqrt(x) - квадратный корень

    awk '{print "Корень из", $1, "=", sqrt($1)}' numbers.txt
  • exp(x), log(x) - экспонента и натуральный логарифм

    awk '{print exp($1)}' data.txt

Тригонометрия и случайные числа

  • sin(x), cos(x), atan2(y, x) - тригонометрия

  • rand() - случайное число от 0 до 1

    awk 'BEGIN{srand(); print int(rand()*100)}'   # число от 0 до 99
  • srand([seed]) - инициализация генератора

    awk 'BEGIN{srand(); for(i=0;i<5;i++) print rand()}'

Временные функции (gawk)

# Текущее время
gawk 'BEGIN{print "Сейчас:", strftime("%Y-%m-%d %H:%M:%S")}'

# Конвертация даты в timestamp
gawk '{timestamp = mktime($1" " $2" " $3" 00 00 00")}' dates.txt

Массивы

Ассоциативные массивы

# Подсчёт частоты слов
awk '{ for(i=1; i<=NF; i++) count[$i]++ }
     END { for(word in count) print count[word], word }' text.txt | sort -rn

# Группировка и суммирование
awk '{ sales[$1] += $3; count[$1]++ }
     END { for(dept in sales) print dept, sales[dept], sales[dept]/count[dept] }' sales.txt

# Проверка существования ключа
awk '{if("key" in arr) print "существует"}'

# Удаление элемента
awk '{delete arr["key"]}'

# Удаление всего массива (gawk)
gawk '{delete arr}'

Обход массива

# Порядок не гарантирован
awk '{ data[$1] = $2 } END { for(key in data) print key, data[key] }'

# Сортировка ключей (через внешнюю сортировку)
awk '{data[$1]=$2} END{for(k in data) print k, data[k]}' | sort

Ввод/вывод

getline - явное чтение

# Чтение из другого файла
awk '{ getline name < "names.txt"; print name, $0 }' data.txt

# Чтение из команды
awk 'BEGIN{ "date +%Y-%m-%d" | getline today; print "Сегодня:", today; close("date") }'

# Чтение нескольких строк
awk '{ while(getline line < "data.txt") print "Чтение:", line; close("data.txt") }'

Перенаправление вывода

# Запись в файл (> перезаписывает)
awk '{print $1 > "output.txt"}' data.txt

# Дозапись в файл (>> добавляет)
awk '{print $2 >> "all.txt"}' data.txt

# Отправка в pipe
awk '{print $0 | "sort -rn > sorted.txt"}' data.txt

# Закрытие файлов (важно при многократном открытии)
awk '{
    file = $1 ".txt"
    print $2 > file
    close(file)
}'

Вывод на экран

# В stderr
awk '{print "Ошибка в строке", NR > "/dev/stderr"}'

# Форматированный вывод
awk '{printf "%-20s %10.2f\n", $1, $2}' data.txt

Практические однострочники

Фильтрация строк

# Пропустить заголовок (первую строку)
awk 'NR > 1' data.txt

# Строки с 10 по 20
awk 'NR >= 10 && NR <= 20' file.txt

# Каждую 3-ю строку
awk 'NR % 3 == 0' file.txt

# Непустые строки
awk 'NF > 0' file.txt

# Строки, начинающиеся с цифры
awk '/^[0-9]/' file.txt

# Строки без цифр
awk '!/[0-9]/' file.txt

# Строки длиной ровно 80 символов
awk 'length == 80' file.txt

Работа с полями

# Вывести определённые столбцы
awk '{print $1, $3, $NF}' data.txt

# Поменять местами столбцы
awk '{print $2, $1}' names.txt

# Удалить первый столбец
awk '{$1=""; sub(/^ /, ""); print}' data.txt

# Удалить последний столбец
awk '{NF-; print}' data.txt

# Суммировать столбец
awk '{sum += $2} END {print "Сумма:", sum}' data.txt

# Среднее по столбцу
awk '{sum += $2; count++} END {print "Среднее:", sum/count}' data.txt

Поиск и замена

# Замена текста во всех полях
awk '{gsub(/foo/, "bar"); print}' file.txt

# Замена только в первом поле
awk '{gsub(/foo/, "bar", $1); print}' file.txt

# Удалить все цифры
awk '{gsub(/[0-9]/, ""); print}' file.txt

# Оставить только цифры
awk '{gsub(/[^0-9]/, ""); print}' file.txt

# Удалить пробелы в начале и конце
awk '{gsub(/^[ \t]+|[ \t]+$/, ""); print}' file.txt

# Сжать множественные пробелы
awk '{$1=$1; print}' file.txt

# Сменить разделитель (табы → запятые)
awk '{$1=$1; print}' OFS=',' file.txt

Статистика

# Минимальное значение
awk 'NR==1 || $1 < min {min=$1} END {print "Минимум:", min}' data.txt

# Максимальное значение
awk '$1 > max {max=$1} END {print "Максимум:", max}' data.txt

# Размах (max - min)
awk 'NR==1 {min=max=$1} {if($1<min)min=$1; if($1>max)max=$1}
     END {print "Размах:", max-min}' data.txt

# Сумма по группам
awk '{sum[$1] += $2} END {for(g in sum) print g, sum[g]}' data.txt

# Количество уникальных значений
awk '!seen[$1]++ {count++} END {print "Уникальных:", count}' data.txt

Работа с дубликатами

# Уникальные строки (первое вхождение)
awk '!seen[$0]++' duplicates.txt

# Только дубликаты
awk 'seen[$0]++' duplicates.txt | sort -u

# Подсчёт дубликатов
awk '{count[$0]++} END {for(line in count) print count[line], line}' | sort -rn

# Уникальные по первому полю
awk '!seen[$1]++' data.txt

Форматирование

# Выравнивание по левому краю
awk '{printf "%-20s %-10s\n", $1, $2}' data.txt

# Выравнивание по правому краю
awk '{printf "%20s %10s\n", $1, $2}' data.txt

# Числа с двумя знаками после запятой
awk '{printf "%.2f\n", $1}' data.txt

# Добавить ведущие нули (ID: 00001)
awk '{printf "ID: %05d\n", $1}' ids.txt

# CSV с кавычками
awk '{printf "\"%s\",\"%s\"\n", $1, $2}' data.txt > output.csv

# Простой JSON
awk '{printf "{\"name\":\"%s\",\"value\":%s}\n", $1, $2}' data.txt

Обработка CSV

# CSV → TSV (табы)
awk -F',' 'BEGIN{OFS="\t"} {$1=$1; print}' data.csv

# TSV → CSV
awk 'BEGIN{FS=OFS="\t"} {$1=$1; print}' OFS=',' data.tsv

# Извлечь столбец по заголовку
awk -F',' 'NR==1{for(i=1;i<=NF;i++) if($i=="Email") col=i} NR>1{print $col}' users.csv

# Проверка целостности (все строки должны иметь одинаковое число полей)
awk -F',' 'NR==1{cols=NF} NF!=cols{print "Ошибка в строке", NR}' data.csv

Обработка логов

# Топ-10 IP-адресов
awk '{ip[$1]++} END{for(i in ip) print ip[i], i}' access.log | sort -rn | head -10

# Подсчёт HTTP статусов
awk '{status[$9]++} END{for(s in status) print s, status[s]}' access.log

# Средний размер ответа
awk '{sum+=$10; count++} END{print "Средний:", sum/count}' access.log

# Ошибки 404
awk '$9 == 404 {print}' access.log

# Запросы за определённый час
awk -F'[][]' '$2 ~ /09\/Mar\/2024:14/' access.log

Преобразование данных

# Развернуть строку в столбец (транспонировать)
echo "a b c" | awk '{for(i=1;i<=NF;i++) print $i}'

# Свернуть столбец в строку
awk '{printf "%s%s", sep, $0; sep=","} END{print ""}' column.txt

# Добавить номер строки
awk '{printf "%3d: %s\n", NR, $0}' data.txt

# Зеркальное отражение полей
awk '{for(i=NF;i>0;i-) printf "%s ", $i; print ""}' data.txt

# Группировка по 3 строки в одну
awk '{printf "%s%s", (NR%3==1 ? "" : " "), $0}
     NR%3==0 {print ""}
     END {if(NR%3!=0) print ""}' data.txt

Продвинутые примеры

Условное форматирование

# Выделить значения выше порога
awk '{
    if($2 > 100)
        printf "%-15s %6.2f ***\n", $1, $2
    else
        printf "%-15s %6.2f\n", $1, $2
}' data.txt

# Цветной вывод (ANSI-коды)
awk '{
    if($2 > 100)
        printf "\033[31m%s %s\033[0m\n", $1, $2   # красный
    else
        print $1, $2
}' data.txt

Скользящее окно

# Анализ тренда (последние 3 значения)
awk '{
    prices[NR] = $2
}
NR >= 3 {
    if(prices[NR] > prices[NR-1] && prices[NR-1] > prices[NR-2])
        trend = "↑↑"
    else if(prices[NR] < prices[NR-1] && prices[NR-1] < prices[NR-2])
        trend = "↓↓"
    else
        trend = "→"
    printf "Строка %d: %s %s %s\n", NR, $1, $2, trend
}' stock.txt

Сравнение с предыдущей строкой

# Поиск резких изменений
awk 'NR > 1 {
    diff = $2 - prev_temp
    if(diff > 10)
        print "Резкое повышение на", NR, ":", prev, "→", $0
    else if(diff < -10)
        print "Резкое понижение на", NR, ":", prev, "→", $0
}
{
    prev = $0
    prev_temp = $2
}' temperatures.txt

Многомерные данные

# Эмуляция двумерного массива
awk '{
    # ключ: отдел_месяц
    sales[$1, $2] += $3
}
END {
    for(comb in sales) {
        split(comb, keys, SUBSEP)
        print keys[1], keys[2], sales[comb]
    }
}' sales.txt

# Таблица по отделам и месяцам
awk '{
    dept = $1
    month = $2
    amount = $3
    depts[dept]
    months[month]
    sales[dept, month] += amount
}
END {
    # Заголовок
    printf "Отдел"
    for(m in months)
        printf "\t%s", m
    print ""
    
    # Данные
    for(d in depts) {
        printf "%s", d
        for(m in months)
            printf "\t%.0f", sales[d, m]
        print ""
    }
}' sales.txt

Генерация отчётов

# Отчёт с итогами
awk 'BEGIN {
    print "================================="
    print "ОТЧЁТ О ПРОДАЖАХ"
    print "================================="
    print sprintf("%-15s %10s %10s", "Регион", "Продажи", "Сделок")
    print "-"
}
{
    sales[$1] += $3
    count[$1]++
    total_sales += $3
    total_count++
}
END {
    for(region in sales) {
        printf "%-15s %10.2f %10d\n", region, sales[region], count[region]
    }
    print "-"
    printf "%-15s %10.2f %10d\n", "ИТОГО:", total_sales, total_count
    print "================================="
}' sales.txt

Отладка

Печать отладочной информации

# В stderr
awk '{print "DEBUG: NR=" NR, "$0=" $0 > "/dev/stderr"; print $0}' data.txt

# Условная отладка
awk '{
    if(DEBUG) print "DEBUG:", $0 > "/dev/stderr"
    # основной код
}' DEBUG=1 data.txt

Профилирование (gawk)

# Профилирование скрипта
gawk -profile=profile.out -f script.awk data.txt

# Просмотр результатов
head -20 profile.out

Отладчик gawk

# Запуск в режиме отладки
gawk -D -f script.awk data.txt

# Команды отладчика:
# breakpoint N  - остановка на строке N
# watch var     - слежение за переменной
# step          - шаг с заходом
# next          - шаг без захода
# print var     - печать значения
# continue      - продолжить
# quit          - выход

Ключевые паттерны для запоминания

# Уникальные строки
awk '!seen[$0]++' file.txt

# Обработка двух файлов
awk 'NR==FNR {a[$1]; next} $1 in a' file1 file2

# Нормализация пробелов
awk '{$1=$1; print}' file.txt

# Непустые строки
awk 'NF > 0' file.txt

# Пропустить заголовок
awk 'NR > 1' file.txt

# Подсчёт строк
awk 'END {print NR}' file.txt

# Сумма столбца
awk '{sum += $1} END {print sum}' file.txt

# Среднее арифметическое
awk '{sum += $1} END {print sum/NR}' file.txt

# Максимальное значение и строка с ним
awk '$1 > max {max = $1; line = $0} END {print line}' file.txt

# Частота слов
awk '{for(i=1;i<=NF;i++) freq[$i]++} END {for(w in freq) print freq[w], w}' | sort -rn

# CSV в TSV
awk -F',' 'BEGIN{OFS="\t"} {$1=$1; print}' file.csv

# Фильтр по условию
awk '$3 > 100 && $3 < 1000' file.txt

# Замена во всей строке
awk '{gsub(/old/, "new"); print}' file.txt

# Вывод с номером строки
awk '{print NR ":", $0}' file.txt

# Сумма по группам
awk '{sum[$1] += $2} END {for(g in sum) print g, sum[g]}' file.txt

Полезные советы

Эффективность

  1. Используйте next для раннего выхода из правил

    /^#/ {print "Комментарий"; next}   # пропустить остальные правила
    {print "Данные:", $0}
  2. Закрывайте файлы при многократной записи

    {print > $1 ".txt"; close($1 ".txt")}
  3. Избегайте getline когда можно обойтись без него

    # Вместо getline используйте обработку двух файлов
    awk 'NR==FNR {a[NR]=$0; next} {print $0, a[FNR]}' file2 file1

Часто используемые регулярки

/^[0-9]/                          # начинается с цифры
/[0-9]$/                          # заканчивается цифрой
/^[A-Z]/                          # начинается с заглавной буквы
/^[[:space:]]*$/                   # пустая строка (только пробелы)
/[0-9]+\.[0-9]+/                   # число с плавающей точкой
/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/  # IP-адрес

Переменные окружения

# Доступ к переменным окружения
awk 'BEGIN{print "HOME:", ENVIRON["HOME"]}'
awk 'BEGIN{print "USER:", ENVIRON["USER"]}'

# Передача переменных из командной строки
awk -v threshold=100 '$3 > threshold' data.txt