Говорят, что изучение bash «по взрослому» начинается с команды awk. И действительно раньше эти конструкции отталкивали, но как оказалось, всё не так страшно как представлялось вначале пути. И теперь меня уже пугают другие bash конструкции, типа сложных условий sed обработки и выборки. Неужели я перешёл на «взрослый» уровень? Не знаю. А вот команда awk понравилась за свою гибкость, ведь в ней доступны условия и даже подскрипты, позволяющие разобрать данные каждой колонки, проверить и обработать их с нужной логикой. Кстати, если быть точным, это уже никакой не bash, это awk!
Допустим у нас есть некий лог файл, в котором через разделитель мы сохраняем инфу с числами. Разобъём его на клонки командой column указав этот разделитель. Ну вот например, подсчёт количества строк с условием, что значение в колонке 9 больше или равно 3:
1 | cat current.log | column -t -s ';' | awk '$9>=3 {print $9}' | wc -l |
Какой grep вам такое выполнит? Нет, силу регулярок, конечно никто не отменял и grep -e справится, но нужно писать регулярку. А если условий будет несколько, придётся писать и под них регулярки. А здесь мы сразу указываем условия напрямую. Ну хорошо, допустим первый пример неудачный, тогда вот такой:
1 2 | cat current.log | column -t -s ';' | awk 'BEGIN{sum=0} {sum+=$9} END{print sum;}' cat current.log | column -t -s ';' | awk 'BEGIN{sum=0} {if($9>=3) sum+=$9} END{print sum;}' |
Вот здесь уж точно только awk справится с такими задачами. В первом случае мы просто подсчитали сумму данных в колонке 9. А второй командой при помощи условия в awk мы подсчитали сумму только тех цифр, что более или равны 3.
Кстати, если в колонке были дробные числа типа float, то и результат будет дробным. А вы знали, что bash при всех своих достоинствах, тупо не работает с дробными числами? При делении они их округляет в меньшую сторону, а при остальных операциях выдаёт ошибку. Попробуем:
1 2 3 | echo $((5/2)) # результат 2 echo $((3+5)) # результат 8 echo $((3.5+5)) # ошибка! |
И как же быть, когда нам нужно произвести в bash операции с дробными числами? Ответ прост: использовать отдельный скриптовый язык awk передав ему управление потоком:
1 | echo 3.5 5 | awk '{print $1+$2}' # результат 8.5 |
Вот почему я сразу заметил, что awk вам не bash! А недавно, у меня возникла ситуация, в которой column отказывался форматировать вывод поскольку в нем присутствовала кириллица. Он просто проглатывал поток и ничего не отдавал. Причём в терминале column разбивл без проблем, проблема была с вызовом shell из под php. А вот awk справился с этой задачей, обработав строки и колонки по разделителю:
1 | cat logfile.log | awk 'BEGIN {FS = ";"}{ for (x = 1; x <= NF; x++) { printf "%-18s%s", substr(\$x, 1, 45), x == NF ? "\\n" : ""} }' |
А вот что он ещё умеет:
Синтаксис команды | Описание | ||
| Печатает только первый и третий столбцы, используя stdin | ||
| Печатает все столбцы, используя stdin | ||
| Печатает только элементы второго столбца, соответствующие шаблону «pattern», используя stdin | ||
| Как и sed, awk использует ключ -f для получения инструкций из файла, что полезно, когда их большое количество и вводить их вручную в терминале непрактично. | ||
| Исполняет program, используя данные из inputfile | ||
| Классическое «Hello, world» на awk | ||
| Печатает все, что вводится из командной строки, пока не встретится EOF | ||
| Скрипт awk для классического «Hello, world!» (сделайте его исполняемым с помощью chmod и запустите) | ||
| Комментарии в скриптах awk | ||
| Определяет разделитель полей как null, в отличие от пробела по умолчанию | ||
| Разделитель полей также может быть регулярным выражением | ||
| Печатает длину самой длинной строки | ||
| Печатает все строки длиннее 80 символов | ||
| Печатает каждую строку, содержащую хотя бы одно поле (NF означает Number of Fields) | ||
| Печатает семь случайных чисел в диапазоне от 0 до 100 | ||
| Печатает общее количество байтов, используемое файлами в текущей директории | ||
| Печатает общее количество килобайтов, используемое файлами в текущей директории | ||
| Печатает отсортированный список имен пользователей | ||
| Печатает количество строк в файле, NR означает Number of Rows | ||
| Печатает четные строки файла. | ||
| Печатает общее количество байтов файла, который последний раз редактировался в ноябре. | ||
| Регулярное выражение для всех записей в первом поле, которые начинаются с большой буквы j. | ||
| Регулярное выражение для всех записей в первом поле, которые не начинаются с большой буквы j. | ||
| Экранирование двойных кавычек в awk. | ||
| Печатает «<A>bcd» | ||
| Модифицирует inventory и печатает его с той разницей, что значение второго поля будет уменьшено на 10. | ||
| Даже если поле шесть не существует в inventory, вы можете создать его и присвоить значение, затем вывести его. | ||
| OFS — это Output Field Separator (разделитель выходных полей) и команда выведет «a::c:d» и «4», так как хотя второе поле аннулировано, оно все еще существует, поэтому может быть подсчитано. | ||
| Еще один пример создания поля; как вы можете видеть, поле между $4 (существующее) и $6 (создаваемое) также будет создано (как пустое $5), поэтому вывод будет выглядеть как «a::c:d::new» «6». | ||
| Отбрасывание трех полей (последних) путем изменения количества полей. | ||
| Это регулярное выражения для установки пробела в качестве разделителя полей. | ||
| Печатает только «a». | ||
| Печатает только первое совпадение с регулярным выражением. | ||
| Устанавливает в качестве разделителя полей \\ | ||
| Если у нас есть запись вида «John Doe 1234 Unknown Ave. Doeville, MA», этот скрипт устанавливает в качестве разделителя полей новую строку, так что он легко может работать со строками. | ||
| Если файл содержит два поля, записи будут напечатаны в виде: «field1:field2 field3;field4 | ||
| Будет напечатано 17 и 18 , так как в качестве выходного формата (Output ForMaT) указано округление чисел с плавающей точкой до ближайших целых значений. | ||
| Вы можете использовать printf практически так же, как и в C. | ||
| Печатает первое поле в виде строки длиной 10 символов, выровненной по левому краю, а затем второе поле в обычном виде. | ||
| Простой пример извлечения данных, где второе поле записывается под именем «phone-list». | ||
| Записывает имена, содержащиеся в $1, в файл, затем сортируем и выводим результат в другой файл. | ||
| Простой поиск для foo или bar. | ||
| Простые арифметические операции (в большинстве похожи на C) | ||
| Простой расширяемый калькулятор | ||
| Печатает каждую запись между start и stop. | ||
| Правила BEGIN и END исполняются только один раз, до и после каждой обработки записи. | ||
| Простое условие. awk, как и C, также поддерживает операторы ?:. | ||
| Печатает первые три поля каждой записи, по одной в строке. | ||
| Печатает первые три поля каждой записи, по одной в строке. | ||
| Выход с кодом ошибки, отличным от 0, означает, что что-то идет не так. Пример: | ||
| Печатает awk file1 file2 | ||
| Удаляет элементы в массиве | ||
| Проверяют элементы массива | ||
| awk-вариант функции ctime() в C. Так вы можете определять свои собственные функции в awk. | ||
| Генератор случайных чисел Cliff. | ||
| Анонимный лог Apache (IP случайные) |
Ну и добавлю ещё базовый синтаксис:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | awk опции 'условие {действие}' awk опции 'условие {действие} условие {действие}' С помощью действия можно выполнять преобразования с обрабатываемой строкой. Об этом мы поговорим позже, а сейчас давайте рассмотрим опции утилиты: -F, --field-separator - разделитель полей, используется для разбиения текста на колонки; -f, --file - прочитать данные не из стандартного вывода, а из файла; -v, --assign - присвоить значение переменной, например foo=bar; -b, --characters-as-bytes - считать все символы однобайтовыми; -d, --dump-variables - вывести значения всех переменных awk по умолчанию; -D, --debug - режим отладки, позволяет вводить команды интерактивно с клавиатуры; -e, --source - выполнить указанный код на языке awk; -o, --pretty-print - вывести результат работы программы в файл; -V, --version - вывести версию утилиты. Это далеко не все опции awk, однако их вам будет достаточно на первое время. Теперь перечислим несколько функций-действий, которые вы можете использовать: print(строка) - вывод чего либо в стандартный поток вывода; printf(строка) - форматированный вывод в стандартный поток вывода; system(команда) - выполняет команду в системе; length(строка) - возвращает длину строки; substr(строка, старт, количество) - обрезает строку и возвращает результат; tolower(строка) - переводит строку в нижний регистр; toupper(строка) - переводить строку в верхний регистр. |