Уроки по Awk
Awk, главным образом, это потоковый редактор вроде sed. Вы можете передавать по трубе текст в эту программу, и она может манипулировать им построчно. Программа также может читать из файла. Ещё awk – это язык программирования. Это в основном означает, что awk может делать всё, что может sed, а также ещё многое другое.
В отличие от sed, awk может помнить контекст, делать сравнения и многие другие вещи, которые могут делать другие языки программирования. Например, она не ограничена единичной строкой. При надлежащей сноровке, она может ОБЪЕДИНЯТЬ множество строк.
Самая простая форма awk выглядит так:
«Здесь_какое-то_действие» может быть просто выражением для печати результата или чем-то намного более сложным. Синтаксис похож на язык программирования ‘C’. Простой пример:
означает напечатать первый и третий столбец, где под столбцами понимаются «вещи, разделённые белым пробелом». Белый пробел = табуляция или пробел.
echo '1 2 3 4' | awk '' 1 3
Часть вторая: Что может делать AWK?
Главная цель в жизни AWK – это манипулировать её вводом на построчной основе. Программа awk обычно работает в стиле
Обработать строку. Двигаться дальше Обработать строку. Двигаться дальше Обработать строку…
Если то, что вы хотите сделать, не вписывается в эту модель, значит awk, может быть, не подходит к вашей задумке.
Обычный используемый в программировании awk синтаксис можно описать так:
awk образец
Это означает, что
«Посмотреть на каждую строку ввода, нет ли там ОБРАЗЦА. Если он там есть, запустить то, что между <>»
Можно пропустить или ОБРАЗЕЦ или КОМАНДУ
Если не указать образец, то команда будет применяться к КАЖДОЙ строке.
Если пропущена команда, то это эквивалентно указанию (просто напечатать строку):
awk '/#/ ' /etc/hosts
будет печатать «В этой строке есть комментарий» для каждой строки, которая содержит хотя бы один ‘#’ в любом месте строки в /etc/hosts
Модификация для наглядности
awk '/#/ ' /etc/hosts
Элемент ‘//’ в образце – это один из способов задать совпадение. Есть также другие способы задать, совпадает ли строка. Например,
awk '$1 == "#" ' /etc/hosts
будет соответствовать строкам, первый столбец в которых является единичным ‘#’. Последовательность символов ‘==’ означает ТОЧНОЕ СОВПАДЕНИЕ ВСЕГО первого столбца.
Модификация для наглядности:
awk '$1 == "#" ' /etc/hosts
С другой стороны, если вы хотите частичное совпадение конкретного столбца, используйте оператор ‘~’
awk '$1 ~ /#/ ' /etc/hosts
ПОМНИТЕ, ЧТО ПЕРВЫЙ СТОЛБЕЦ МОЖЕТ БЫТЬ ПОСЛЕ БЕЛОГО ПРОБЕЛА.
Модификация для наглядности:
awk '$1 ~ /#/ ' /etc/hosts
Ввод «# comment» будет соответствовать
Ввод » # comment» будет ТАКЖЕ соответствовать
Если вам нужно конкретное совпадение «строка, которая начинается точно с # и пробела», вы должны использовать
Множественное совпадение
Awk обработает ВСЕ ОБРАЗЦЫ, которые соответствуют текущей строке. Поэтому если использовать следующий пример,
awk ' /#/ $1 == "#" /^# / ' /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 a secret addr ADDR another secret addr a 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 are 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 amelia.zodiacusque@gmail.com F Anthony 555-3412 anthony.asserturo@hotmail.com A Becky 555-7685 becky.algebrarum@gmail.com A Bill 555-1675 bill.drowning@hotmail.com A Broderick 555-0542 broderick.aliquotiens@yahoo.com R Camilla 555-2912 camilla.infusarum@skynet.be R Fabius 555-1234 fabius.undevicesimus@ucb.edu F Julie 555-6699 julie.perscrutabor@skeeve.com F Martin 555-6480 martin.codicibus@hotmail.com A Samuel 555-3430 samuel.lanceolis@shu.edu A Jean-Paul 555-2127 jeanpaul.campanorum@nyu.edu 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) " '' 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 '/[aeiou]/' /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 (условие) тело-тогда [else тело-ещё]
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' 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.26536e-05 -1 1.00387
Программа может сгенерировать случайное число в диапазоне (0, 1).
awk 'BEGIN < print rand(); print rand() >' 0.237788 0.291066
По умолчанию 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.255219 0.898883
Функция 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", one, two)
- length() просто даёт вам простой способ подсчитать количество символов в строке, если вам это понадобится.
substr
Функция substr(s, m, n) возвратит подстроку в n-символов, начинающуюся с позиции m, отсчитанной от 1.
awk '< print $1, substr($1, 2, 3) >' field_data.txt Roses ose Violets iol Sugar uga And nd
index
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
match(s, r) возвращает позицию в s в которой встречается регулярное выражение r, или 0 если оно не встречается. Переменные RSTART и RLENGTH устанавливаются в позицию и длину совпадающей строки.
match – это как index кроме того, что паттерн является регулярным выражением.
awk '< print $1, match($1, "[sS]") >' field_data.txt Roses 3 Violets 7 Sugar 1 And 0
# "Поиск трёх или более повторяющихся букв" awk '< match($1, "[a-z]"); 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
split(s, a, fs) расщепляет строку на массив элементов a[1], a[2], …, a[n], и возвращает n.
Разделение делается по регулярному выражению fs или с разделителем полей FS, если fs не дан. Пустая строка в качестве разделителя полей расщепляет строку в массив элементов посимвольно.
awk 'BEGIN < print split("It-was_the-best_of-times", output_array, "[-_]"), output_array[2], output_array[4] >' 6 was best
Практический пример команды, которая из выводимых данных выбирает версию Bluetooth и «конвертирует» её в понятную нам версию (в соответствии с официальной документацией):
btmgmt info | awk 'BEGIN < split("1.0b 1.1 1.2 2.0 2.1 3.0 4.0 4.1 4.2 5.0 5.1 5.2 5.3",i," ") >$1=="addr"'
Строка split("1.0b 1.1 1.2 2.0 2.1 3.0 4.0 4.1 4.2 5.0 5.1 5.2 5.3",i," ") разбивает массив "1.0b 1.1 1.2 2.0 2.1 3.0 4.0 4.1 4.2 5.0 5.1 5.2 5.3" на подстроки, используя в качестве разделителя пробел и присваивает значение полученного массива переменной i.
Команда $1=="addr"' распространяет своё действие только на те строки, в которых первое поле является строкой addr. Для таких строк выводится следующее:
- строка «Bluetooth: V»
- одно из значений массива i, индекс выводимого значения вычисляется как значение четвёртого поля к которому прибавлена единица
sub
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
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[1]="one"; values[2]="two"; values[3]="three";
Первый пример – это три разных переменных со своими именами (которые различаются на один символ). Второй пример – это массив, который состоит из одной переменной, но содержит много значение, каждое из которых имеет свой номер.
При использовании переменной в качестве массива, всегда нужно помещать значение в квадратные скобки []. Вы можете выбрать любое имя для переменной массива, но с этого момента это имя можно использовать ТОЛЬКО для массива. Вы НЕ МОЖЕТЕ осмысленно делать
values[1]="one"; values="newvalue";
Тем не менее, вы МОЖЕТЕ переназначить величины, как для нормальных переменных. Т.е. следующее ЯВЛЯЕТСЯ правильным:
values[1]="1"; print values[1]; values[1]="one"; print values[1];
Интересно то, что в отличие от некоторых других языков, вы не обязаны использовать только номера. В примерах выше [1],[2],[3] на самом деле истолковываются как [«1»], [«2»], [«3»]. Что означает, что вы также можете использовать другие строки в качестве идентификаторов, и рассматривать массив почти как базу данных с одной колонкой. Официальное название для этого «ассоциированный массив».
numbers["one"]=1; numbers["two"]=2; print numbers["one"]; value="two"; print numbers[value]; value=$1; if(numbers[value] = "")
Когда и как использовать массивы
Могут быть различные случае, когда вы можете выбрать использование массивов. Некоторые при работе с awk вообще обходятся без массивов. Но это не совсем верная позиция: для массивов существуют специальные переменные, которые, например, показывают его размер (количество значений в массиве), имеются удобные для перебора членов массива конструкции, некоторые функции возвращают значение в виде массива. В любом случае, давайте рассмотрим несколько примеров, которые могут пригодиться.
Сохранение информации для дальнейшего использования
При использовании awk в большом скрипте оболочки, можно сохранять информацию во временный файл. Но можно сохранять нужные слова в память, а затем все их напечатать в конце, что будет быстрее, чем использование временного файла.
/special/ < savedwords[lnum]=$2; lnum+=1; >END < count=0; while(savedwords[count] != "") < print count,savedwords[count]; count+=1; >>
Вместо простого вывода слов, вы можете использовать секцию END чтобы перед их отображением сделать дополнительную обработку, которая вам может быть нужна.
Если вы хотите присвоить уникальный индекс значениям (для избегания дубликатов), вы вообще можете отсылать к их значения по их собственным строкам. Или, например, сохранить массив с колонкой 3, проиндексированный по соответствующему значению колонки 2.
Массивы и функция split()
Другая главная причина для использования массивов, если вы хотите делать подполя. Допустим у вас есть строка, которая имеет несколько крупных подразделений и несколько мелких подразделений. Другими словами, Поля верхнего уровня разделяются пробелами, но затем вы получаете более маленькие слова, разделённые двоеточиями.
This is a variable:field:type line There can be multiple:type:values here
В примере выше четвёртое отделённое пробелом поле имеет подполя, разделённые двоеточиями. Теперь, допустим, вы хотите узнать значение второго подполя в четвёртом большом поле. Один из способов сделать это, вызвать две awk связанные трубой:
awk '' | awk -F: ''
Другим способом будет на лету изменить значение 'FS', которая содержит разделитель полей (судя по всему, это работает не со всеми реализациями awk):
Но вы также можете сделать это с массивами, используя функцию split() следующим образом:
В этом случае использование массива это самый обычный и, возможно, самый изящный способ сделать это.
Итак, Awk обеспечивает ограниченное количество структур данных. Кроме скалярных и строковых переменных, в язык встроена структура массивных данных. Хотя официально она называется «массивы», эта структура на самом деле является ассоциированным массивом, аналогичной структуре данных dict в Python.
Массивы не нужно инициализировать. Вы можете просто начать присваивать значения. Обратите внимание, что ключами могут быть цифры или строки.
awk 'BEGIN < \ a[0] = 1.1; \ a[0.83] = 0; \ a["DOG"] = "CAT"; \ print a[0], a[0.83], 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 a scalar context
Хотя мы можем сделать цикл по ключу используя for:
awk 'BEGIN < \ a[0] = 1.1; \ a[0.83] = 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\" '' | sort -u
Вопросы и ответы по awk
Как вывести только строку определённого номера в awk
Чтобы вывести строки с определённым номером, используйте if() и переменную NR.
Например, чтобы вывести только вторую строку:
free | awk '< if (NR == 2) print $7 >'
Чтобы вывести вторую и все последующие строки:
free | awk '< if (NR >= 2) print $7 >'
Чтобы вывести все строки с 10 по 20:
awk '< if (NR >= 10 && NR ' /etc/passwd
Как перенаправить вывод в файл в awk
Команду print можно использовать с перенаправлением вывода в файл.
К примеру, следующая команда сохранит строки с 10 по 20 из файла /etc/passwd в файл pswd.txt:
awk '< if (NR >= 10 && NR "pswd.txt" >' /etc/passwd
Следующая команда ищет в строке слово «mial» и если оно там встречается, то сохраняет всю строку в файл pswd.txt:
awk '/mial/< print>"pswd.txt" >' /etc/passwd
Как использовать переменные в awk
Следующая команда посчитает количество строк содержащих слово «bash» в файле /etc/passwd, выведет каждую из этих строк и затем выведет общее количество найденных строк:
awk -v y=0 '/bash/ < y++; print $0 >END < print "Всего найдено строк с bash: " y >' /etc/passwd
При запуске программы инициируется переменная y со значением 0. При каждом совпадении (найдена строка «bash»), значение y увеличивается на единицу и выводится найденная строка. В конце выводится значение y.
Как вывести скобки и другие специальные в awk
Скобки и другие специальные символы необходимо помещать в двойные кавычки.
awk -F ',' '< print "(" $1, $2 ")" >'
В предыдущих примерах имя файла также помещено в двойные кавычки из-за содержащейся в нём точки, которая является специальным символом.
Как удалить символы newline из вывода команд
С помощью awk вы можете удалить символы newline (новой строки) используя следующую конструкцию:
echo 'HackWare.ru' | awk '< printf "%s", $0 >' | md5sum
Ещё один вариант:
echo 'HackWare.ru' | awk '' | md5sum
Как заменить символы newline на пробелы в файле
Следующая команда прочитает файл FILE, заменит в нём newline на пробелы и выведен полученный результат на экран:
awk 1 ORS=' ' FILE
Программа awk состоит из правил, состоящих из условных кодовых блоков, то есть:
условие
Если кодовый блок опущен, используется значение по умолчанию: . Таким образом, 1 всегда интерпретируется как истинное условие, и для каждой строки выполняется print $0.
Когда awk читает ввод, он разбивает его на записи на основе значения RS (Record Separator, разделитель записей), который по умолчанию является newline (новой строкой), поэтому awk по умолчанию будет анализировать ввод построчно. Разделение также включает удаление RS из входной записи.
Теперь при печати записи к ней добавляется ORS (Output Record Separator, разделитель выходных записей), по умолчанию снова newline. Таким образом, поскольку мы заменили значение ORS на пробел, все символы новой строки заменяются пробелами.
Ещё один вариант, чтобы заменить все новые строки пробелами с помощью awk, не считывая весь файл в память:
awk '' FILE
Если вы хотите, чтобы присутствовал финальный newline:
awk ' END ' FILE
Вы можете использовать не только символ пробела (в данном случае вместо пробела разделителем является символ «|»):
awk ' END ' FILE
Ещё одно просто решение на awk:
awk '' FILE
Как вывести с n-го столбца до последнего
- как вывести со второго столбца до последнего
- как вывести с третьего столбца до последнего
- как вывести с четвёртого столбца до последнего
- как вывести с n-го столбца до последнего
Как вывести с n-го столбца до последнего:
В awk вы можете использовать конструкцию вида:
Этот пример очистит содержимое первых трёх столбцов и покажет все колонки, начиная с четвёртого столбца.
Но при этом пробелы, который находятся между первым и вторым столбцом, вторым и третьим столбцом, а также третьим и четвёртым столбцом удалены не будут и будут показаны. Чтобы их удалить используйте дополнительно команду sed:
awk '' | sed -r "s/^\s+//g"
Либо вы можете использовать следующую команду awk которая очищает пробелы между удалёнными стобцами:
Как вывести со второго столбца до последнего:
awk '' | sed -r "s/^\s+//g" awk ''
echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk '' | sed -r "s/^\s+//g" echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk ''
Как вывести с третьего столбца до последнего:
awk '' | sed -r "s/^\s+//g" awk ''
echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk '' | sed -r "s/^\s+//g" echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk ''
Как вывести с четвёртого столбца до последнего:
awk '' | sed -r "s/^\s+//g" awk ''
echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk '' | sed -r "s/^\s+//g" echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk ''
Как вывести последний столбец
Для вывода последнего столбца с помощью awk используйте команду:
echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' | awk ''
Почему awk неправильно определяет границы данных, разделённых табуляцией
Следующая команда вместо ожидаемого третьего столбца вернёт пустой результат:
echo '1 2 3 4 5 6' | awk -F'\t' '< print $3 >'
В команде вместо стандартного FS (Input field separator, разделитесь полей входных данных), которым по умолчанию является пробел, опцией -F'\t' установлен новый разделитесь, в качестве которого указан «\t», что означает символ табуляции.
Проблема предыдущей команды в том, что во входных данных поля на самом деле не разделены символами табуляции, а разделены несколькими пробелами.
То есть использовании опции -F не нужно в предыдущей команде:
echo '1 2 3 4 5 6' | awk '< print $3 >' 3
Несмотря на то, что данные разделены несколькими пробелами, указывать это с опцией -F не нужно, поскольку она правильно истолковывает ввод. По умолчанию разделителем полей в awk служит один или больше пробелов (пробельные символы или символ табуляции), что соответствует [ \t]+ или если использовать posix классы [[:blank:]]+
Именно поэтому даже если данные на самом деле разделены табуляцией, команда awk обрабатывает их правильно:
echo '1 2 3 4' | awk '< print $3 >' 3
В этом случае опция -F'\t' работает как и ожидается:
echo '1 2 3 4' | awk -F'\t' '< print $3 >' 3
Нужно отметить, что разделитель полей в awk является регулярным выражением. Поэтому идущие подряд повторяющиеся символы, выбранные в качестве разделителей столбцов, трактуются как единый разделитесь между двумя смежными полями.
Чтобы проверить, какие именно непечатные символы присутствуют во вводимых данных, используйте cat -A. Например:
echo '1 2 3 4' | cat -A 1M-bM-^PM-^A^IM-oM-?M-=2^I3 4$
Как сделать так, чтобы awk выводила поля разделяя их табуляцией
Следующая команда выведет третью и четвёртую колонку, разделяя их пробелом:
echo '1 2 3 4 5' | awk '< print $3,$4 >' 3 4
Если вы хотите, чтобы выводимые данные разделялись табуляцией (или любым другим символом), то его нужно установить в качестве значения OFS (output field separator, разделитесь выходных полей). Например:
echo '1 2 3 4 5' | awk 'BEGIN ; < print $2,$3 >' 2 3
OFS вставляется между полями, перечисленными через запятую, то есть следующая команда не выведет табуляции между полями (и даже не выведет пробел):
echo '1 2 3 4 5' | awk 'BEGIN ; < print $2 $3 >' 23
Кроме изменения значения OFS (output field separator, разделитесь выходных полей) вы можете указать символ табуляции в шаблоне вывода. Например, следующая команда для разделения второго и третьего поля будет использовать стандартный OFS (то есть пробел), а между третьей и четвёртой колонкой будет вставлен знак табуляции:
echo '1 2 3 4 5' | awk '< print $2,$3"\t"$4 >' 2 3 4
Примеры решения задач с помощью awk
Задача 1.
Есть файл вот с таким содержимым:
ОРГАНИЗАЦИЯ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| ОРГАНИЗАЦИЯ НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ| НАЧИСЛЕНИЕ|
Таких строк может быть любое количество, как в меньшую, так и в большую сторону. После слов "ОРГАНИЗАЦИЯ" строка продолжается наименованием и реквизитами организации.
После слов "НАЧИСЛЕНИЕ" строка продолжается суммой начисления и прочими данными.
Разделителем является труба: |
Задача такая — с помощью awk найти слово "ОРГАНИЗАЦИЯ", скопировать содержимое этой строки и привязанные к ней строки "НАЧИСЛЕНИЕ".
Т.е. первую строку "ОРГАНИЗАЦИЯ" и две строки ниже со словом "НАЧИСЛЕНИЕ". Вывести эти 3 строки в отдельный файл. Количество строк может быть любым.
Найти следующие строки по такому же принципу и опять в отдельный файл.
По этому примеру должно быть 6 файлов и каждый со своей организацией и начислениями.
Решение 1. Работает в awk под Linux
awk -v y=0 '/ОРГАНИЗАЦИЯ/y; x=$0 ;y++> !/ОРГАНИЗАЦИЯ/ END y>' test.txt
Решение 2. Работает в awk под Windows
awk.exe -v y=0 "/ОРГАНИЗАЦИЯ/y\".txt\";> !/ОРГАНИЗАЦИЯ/y\".txt\">" test.txt
Входные данные в файле test.txt. Тестировалась в cmd (а не в PowerShell).
Содержимое первых четырёх файлов:
Задача 2.
Как добавить в существующий файл строку с нужными мне данными?
Т.е. есть файл, например, с 6 строками и я хочу добавить сроку перед 1 строкой или добавить строку во все .txt файлы в определённой папке.
Решение 1. Работает в awk под Linux
Итак, следующий пример считывает все файлы из текущего каталога (*). Если нужно, чтобы считывал только файлы с определённым разрешением, то вместо звёздочки можно записать, например, так *.txt.
Для каждого обрабатываемого файла в самое начало добавляется строка "СТРОКА" — замените на нужную. Новые файлы сохраняются в эту же папку, с такими же именами, но перед именем добавляется префикс "new_" - при желании, его можно удалить или заменить на другой (в двух местах команды!).
awk -v 'OLD_FILENAME=""' ' "new_"FILENAME; OLD_FILENAME=FILENAME> "new_"FILENAME;>' *
- при запуске awk инициализируется переменная OLD_FILENAME — которой в качестве значения присваивается пустая строка
- при обработке каждой строки, значение переменной OLD_FILENAME сравнивается со значением встроенной переменной FILENAME в которой (сюрприз!) содержится имя текущего файла. Если эти значения НЕ равны, то в файлы печатается строка "СТРОКА" И переменной OLD_FILENAME присваивается значение, которое содержит FILENAME.
- следующая часть "new_"FILENAME;> просто печатает очередную строку в новый файл
- при последующих прохождениях (вторая строка, третья строка и т. д.) значения OLD_FILENAME и FILENAME будут одинаковыми. Вплоть до момента, пока обрабатываемый файл не сменится на новый. В этот момент OLD_FILENAME и FILENAME становятся не равны и в новый файл первой печатается строка "СТРОКА" и переменной OLD_FILENAME присваивается значение FILENAME
- всё продолжается, пока все строки во всех файлах не кончатся
Решение 2. Работает в awk под Windows
Для Windows (cmd, а не PowerShell):
awk.exe -v OLD_FILENAME="" " \"new_\"FILENAME; OLD_FILENAME=FILENAME> \"new_\"FILENAME;>" *.txt
Связанные статьи:
- Как отфильтровать текст, находящийся между двумя определёнными строками (100%)
- Как добавить строку в начало или в конец каждой строчки (82.2%)
- Как сделать замену от совпадения до конца строки (82.2%)
- Как добавить нули до определённого размера строки (82.2%)
- Как удалить newline (символ новой строки) из вывода команд и файлов в командной строке Linux (82.2%)
- Что такое ZSH и почему его следует попробовать в качестве альтернативы Bash? (RANDOM - 17.9%)
Рекомендуемые статьи:
7 Комментарии
syntx 21.08.2018 в 12:15
Занимательная статья, хочу поблагодарить за труды. Нашёл в ней не точность. Если выполнить строку из примера
awk -F " " '< print $2 >' field_data.txt
она выведет тоже самое, что и
awk '< print $2 >' field_data.txt
Получается пример с -F не удачно описан.
Alexey (Автор записи) 21.08.2018 в 13:40
Спасибо за полезное замечание. Добавил ещё один пример, в котором в качестве разделителя используется запятая.
redking 16.01.2022 в 22:40
несколько мелких ремарок по тексту:
AWK также имеет множество других специальных величин, которые вы можете использовать в секции < >.
полагаю что "которые вы можете использовать в секции < >" лишнее, так как наводит на мысль что NF можно использовать только в пределах <>, а это не совсем так, например:
awk 'NF!=0'
удалит все пустые строки из вывода
awk 'BEGIN < printf "%-10s %s\n", "Name", "Number" \
printf "%-10s %s\n", "----", "------" > \
< printf "%-10s %s\n", $1, $2 >' mail-data
в awk перенос записи команд с помощью слеша(\) как в bash не работает, вернее оно(\) либо просто проигнорируется либо выдаст ошибку, примеры шаблонов: print …>
ОШИБКА (когда используется как разделитель между командами) print …>
правильно
правильно print …>
правильно но бесмысленно, в данном случае перенос строки срабатывает как разделитель команд ';' даже при if else и др. print …>
правильно но вдвойне бесмысленно там далее по тексту есть еще код с лишними '\' но думаю примера выше будет достаточно для общей картины.
awk '/[aeiou]/' /usr/share/dict/words
cadiueio
Chaouia
euouae
Guauaenok
данный файл может быть не у всех, по крайней мере в archlinux
судя по статье Words_(Unix) из wiki, для Debian/Ubuntu нужен пакет wordlist, а для Arch и Fedora нужен пакет words ; и действительно в Arch-e после установки пакета words появляется данный файл, НО даже после установки нужного пакета вышеописанная команда awk не выдаст нужного результата так как не найдёт в файле /usr/share/dict/words таких слов, не знаю как в другиг дистрах но похоже со временем что-то поменялось и данные слова потёрли с этого словаря. Как вариант можно установить пакет wordlists из AUR-a там есть куча всяких словарей по адресу /usr/share/wordlists/ и данные слова находятся, но проблема в том что там очень много разных словарей общим объёмом порядка 1,8G
# "Поиск трёх или более повторяющихся букв"
awk '< match($1, "[a-z]"); print $1, "\tpattern start:", RSTART, "\tpattern end:", RLENGTH >' letters.txt
тут скорее всего правильнее будет сказать не повторяющихся, а в подряд идущих букв, ведь помимо aaa сочетание abc тоже будет верным.
sprintf
sprintf(fmt, expr, … ) returns the string resulting from formatting expr … according to the printf(3) format fmt
думается мне что эта часть кода не совсем "раскрыта" в тексте
Другим способом будет на лету изменить значение 'FS', которая содержит разделитель полей (судя по всему, это работает не со всеми реализациями awk):
тут, кстати, можно немного упростить и не вводить лишних переменных:
awk ''
awk ' < newline=$4; split(newline,subfields,":"); print subfields[2]>'
В этом случае использование массива это самый обычный и, возможно, самый изящный способ сделать это.
в простых случаях, коих большинство, у нас фиксированная структура поступающих данных, а если мы и так знаем где находится нужная подстрока($4) то проще будет составить подходящий разделитель:
awk -F' |:' ''
даст тот же результат а вот пример боле сложного случая, когда искомые подстроки могут идти после других подстрок с теми же разделителями, и они имеют разное количество елементов
1 2 a1:b1 a1:b1:c1 3
1 2 a2:b2:c2 a2:b2:c2 3 вот здесь уже да, без вышеописанных вами методов трудно обойтись
Чтобы вывести строки с определённым номером, используйте if() и переменную NR.
вообщето без if тоже можно обойтись:
free |awk 'NR==2'
free |awk 'NR>=2'
awk 'NR>=10 && NR От себя хотел бы еще добавить, возможно кому-то будет полезно, что аналогом ключа -i у sed для awk является -i inplace, то есть:
awk -i inplace '' file.txt
все обработанные данные из file.txt запишутся в этот же файл об это кстати говорится в статье Как добавить строку в начало или в конец каждой строчки но там указанно только -i возможно просто опечатка.
Alexey (Автор записи) 19.01.2022 в 13:52
Приветствую! Спасибо большое за такие подробные и полезные замечания — мне нужно некоторое время, чтобы «переварить» информацию — в любом случае, я учту всё написанное и исправлю данный материал. Надеюсь, это оценят и другие изучающие awk. Также планирую добавить примеры из man awk. Единственное что у меня вызывает дополнительные вопросы, это целесообразность обратного слэша в качестве разделителя строк. К примеру, следующую команду я могу целиком скопировать-вставить, либо набрать посимвольно и она выполнится без ошибок.
awk \ 'BEGIN < \ i = 0; \ while (i < 5) < print i; i+=1; >\ >'
А уже без обратных слэшей при копировании-вставке целиком будет ошибка:
awk 'BEGIN < i = 0; while (i < 5) < print i; i+=1; >>'
Что касается примера
awk 'BEGIN < printf "%-10s %s\n", "Name", "Number" \ printf "%-10s %s\n", "----", "------" >\ < printf "%-10s %s\n", $1, $2 >' mail-data
то он как будто сам по сам по себе содержит ошибку — по крайней мере, у меня он не работал даже в одну строку — я добавил недостающую точку с запятой в этом примере.
redking 19.01.2022 в 21:46
А уже без обратных слэшей при копировании-вставке целиком будет ошибка:
есть область видимости awk, а есть область видимости bash и слеши у них будут действовать по разному правильнее будет так: awk \ 'BEGIN i = 0 while (i >' если упростить: awk \ -F':' \ '… а здесь уже действуют законы awk …'
Что касается примера
awk 'BEGIN < printf . \ printf . >\ < printf . >' mail-data ошибка из за того что слеш между двумя printf в одном блоке<>, то есть, две подряд идущие команды в одном блоке должны быть разделены либо точкой с запятой(;) и/или переносом строки awk 'BEGIN < printf . printf . > < printf . >' mail-data или awk 'BEGIN < printf . ; printf . > < printf . >' mail-data или awk 'BEGIN < printf . >' mail-data слеш не будет вызивать ошибку когда ставиш его в конце/начале блока<> или после ; при переносе , но еще раз, смысла его ставить нету, тупо лишние символы которые при том вносят путаницу ))
redking 20.01.2022 в 17:10
кстати, к вышеизложенному, в man awk по поводу
One convention that might not be obvious from the formal grammar is where characters are acceptable. There are several obvious placements such as terminating a statement, and a can be used to escape characters between any lexical tokens. In addition, characters without characters can follow a comma, an open brace, logical AND operator ("&&"), logical OR operator ("||"), the do keyword, the else keyword, and the closing parenthesis of an if, for, or while statement. For example: < print $1,
$2 >
и там же, но чуть ниже:
4. A character immediately followed by a shall have no effect.
п.с. выше в комментариях я писал о слеше, извиняюсь, имелось в виду обратный слеш.
Alexey (Автор записи) 20.01.2022 в 17:44
Спасибо за дополнения и разъяснение про области видимости bash и awk — теперь мне стало действительно понятно.
Команда awk – примеры использования в Linux и Unix
В этом базовом руководстве вы узнаете самые основы команды awk , а также увидите некоторые способы её использования при работе с текстом, включая вывод содержимого файла, а также его конкретных столбцов, строк и слов по указанным критериям. Приступим!
Что это за команда awk?
AWK – это скриптовый язык, который полезен при работе в командной строке и широко применяется для обработки текста.
При использовании awk вы можете выбирать данные – один или более отдельных фрагментов текста – на основе заданного критерия. Например, с помощью awk можно выполнять поиск конкретного слова или шаблона во фрагменте текста, а также выбирать определённую строку/столбец в файле.
Базовый синтаксис awk
Простейшая форма команды awk подразумевает описание основного действия в одинарных кавычках и фигурных скобках с указанием после него целевого файла.
Выглядеть она может так:
awk '' your_file_name.txt
Когда вам нужно найти текст, соответствующий конкретному шаблону, или же конкретное слово в тексте, команда принимает следующий вид:
awk '/regex pattern/' your_file_name.txt
Создание образца файла
Для создания файла в командной строке используется команда touch . Например: touch filename.txt , где filename – это произвольное имя файла.
Затем можно с помощью команды open ( open filename.txt ) запустить обработчик текста вроде TextEdit, который позволит внести в файл нужное содержимое.
Предположим, у вас есть текстовый файл information.txt, содержащий данные, разделённые по столбцам.
Выглядеть этот файл может так:
firstName lastName age city ID Thomas Shelby 30 Rio 400 Omega Night 45 Ontario 600 Wood Tinker 54 Lisbon N/A Giorgos Georgiou 35 London 300 Timmy Turner 32 Berlin N/A
В приведённом примере мы видим по одному столбцу для
firstName , lastName , age , city и ID .
В любой момент можно просмотреть вывод содержимого вашего файла, выполнив cat text_file , где text_file представляет имя файла.
Вывод всего содержимого файла
Для вывода всего содержимого файла в качестве действия в фигурных скобках нужно указать print $0 .
Сработает эта команда аналогично ранее упомянутой cat .
awk '' information.txt
firstName lastName age city ID Thomas Shelby 30 Rio 400 Omega Night 45 Ontario 600 Wood Tinker 54 Lisbon N/A Giorgos Georgiou 35 London 300 Timmy Turner 32 Berlin N/A
Если захотите добавить нумерацию строк, то нужно будет дополнить действие переменной NR :
awk '' information.txt
1 firstName lastName age city ID 2 3 Thomas Shelby 30 Rio 400 4 Omega Night 45 Ontario 600 5 Wood Tinker 54 Lisbon N/A 6 Giorgos Georgiou 35 London 300 7 Timmy Turner 32 Berlin N/A
Вывод конкретных столбцов
При использовании awk можно указывать для вывода конкретные столбцы.
Вывод первого производится следующей командой:
awk '' information.txt
Thomas Omega Wood Giorgos Timmy
Здесь $1 означает первое поле, то есть в данном случае первый столбец.
Для вывода второго столбца используется $2 :
awk '' information.txt
lastName Shelby Night Tinker Georgiou Turner
По умолчанию начало и конец каждого столбца awk определяет по пробелу.
Для вывода большего числа столбцов, например, первого и четвёртого, нужно выполнить:
awk '' information.txt
firstName city Thomas Rio Omega Ontario Wood Lisbon Giorgos London Timmy Berlin
Здесь $1 представляет первое поле ввода (первый столбец), а $4 четвёртое. При этом они отделяются запятой, чтобы вывод разделялся пробелом и был более читаемым.
Для вывода последнего поля (последнего столбца) также можно использовать команду $NF , представляющую последнее поле записи:
awk '' information.txt
ID 400 600 N/A 300 N/A
Вывод конкретных строк столбца
Также можно указывать для вывода строку определённого столбца:
awk '' information.txt | head -1
FirstName
Разделим эту команду на две части. Сначала awk '' information.txt выводит первый столбец. Затем её результат (который мы видели выше) с помощью символа | передаётся на обработку команде head , где аргумент -1 указывает на выбор первой строки столбца.
Для вывода двух строк команда будет такой:
awk '' information.txt | head -2
FirstName Dionysia
Вывод строк с заданным шаблоном
Вы можете выводить строку, начинающуюся с заданной буквы. Например:
awk '/^O/' information.txt
Omega Night 45 Ontario 600
Эта команда выбирает все строки с текстом, начинающимся на O .
Действие команды начинается с символа ^ , который указывает на начало строки. После этого прописывается буква, с которой нужная вам строка должна начинаться.
По аналогичному принципу можно выводить строку, завершающуюся конкретным шаблоном:
awk '/0$/' information.txt
Thomas Shelby 30 Rio 400 Omega Night 45 Ontario 600 Giorgos Georgiou 35 London 300
Эта команда выводит строки, оканчивающиеся на 0 – здесь с помощью символа $ мы указываем, как должна заканчиваться нужная строка.
При этом её можно несколько изменить:
awk '! /0$/' information.txt
Символ ! используется в качестве приставки «НЕ», а значит, в этом случае будут выбраны строки, которые не оканчиваются на 0 .
firstName lastName age city ID Wood Tinker 54 Lisbon N/A Timmy Turner 32 Berlin N/A
Использование регулярных выражений
Для вывода слов, содержащих определённые буквы, а также слов, соответствующих указанному шаблону, мы снова используем прямые слэши.
К примеру, если нас интересуют слова, содержащие io , мы пишем:
awk ' /io/' information.txt
Thomas Shelby 30 Rio 400 Omega Night 45 Ontario 600 Giorgos Georgiou 35 London 300
Мы получили строки, в которых содержатся слова, содержащие io .
Теперь предположим, что в файле есть дополнительный столбец department :
firstName lastName age city ID department Thomas Shelby 30 Rio 400 IT Omega Night 45 Ontario 600 Design Wood Tinker 54 Lisbon N/A IT Giorgos Georgiou 35 London 300 Data Timmy Turner 32 Berlin N/A Engineering
Для поиска всей информации о людях, работающих в IT , нужно указать искомую строку между // :
awk '/IT/' information.txt
Thomas Shelby 30 Rio 400 IT Wood Tinker 54 Lisbon N/A IT
А что, если мы хотим увидеть только имена и фамилии сотрудников из IT ?
Тогда можно указать столбец так:
awk '/IT/' information.txt
Thomas Shelby Wood Tinker
В этом случае отобразятся только первый и второй столбцы строк, содержащих IT .
При поиске слов, содержащих конкретный шаблон, бывают случаи, когда требуется использовать экранирующий символ:
awk '/N\/A$/' information.txt
Wood Tinker 54 Lisbon N/A Timmy Turner 32 Berlin N/A
Я хотела найти строки, оканчивающиеся на N/A . Поэтому при указании критериев поиска в ' // ' , как это показывалось выше, мне пришлось использовать между N/A символ перехода \ . В противном случае возникла бы ошибка.
Использование операторов сравнения
Если вы, предположим, захотите найти всю информацию о сотрудниках в возрасте до 40 лет, то нужно будет использовать оператор сравнения < так:
awk '$3 < 40 < print $0 >' information.txt
Thomas Shelby 30 Rio 400 Giorgos Georgiou 35 London 300 Timmy Turner 32 Berlin N/A
В выводе представлена информация о людях моложе 40.
Заключение
Вот и всё. Теперь у вас есть необходимая основа для начала работы с awk и управления текстовыми данными.
Благодарю за чтение и успехов вам в обучении!
- ruvds_перевод
- awk. linux
- Блог компании RUVDS.com
- Настройка Linux
- Системное администрирование
- *nix
Что означает 0 внутри команды awk
Есть файл с содержимым типа:
a = b
b = c
a = b
a = b
a = b
b = c
a = b
awk '{sub ($1, "'"$res"'"); print}' $f2 #вставка 4 полей в другой файл
как можно заменить 4 поля в определенной строке или, написать скрипт в одной строке с помощью sed или awk
Username: Александр
ROLE_USER
ROLE_ADMIN
ROLE_SUPER_ADMIN
ROLE_SUPPORT_CHAT
ROLE_TAG_EDITOR
ROLE_TEST2
Username: Rus10Neo
ROLE_USER
ROLE_SONATA_EDITOR
ROLE_SONATA_READER
ROLE_SONATA_ADMIN
ROLE_FOOTBALL_PLAYER_MERGE
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_STAFF
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_BET_LINK_EDITOR
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_EDITOR
ROLE_ALL_TIPS_EDITOR
ROLE_INTELL_ADMIN_ADMIN_FOOTBALL_MEETING_TV_CHANNEL_ADMIN
ROLE_TIP_LOCALE_ADMIN
Username: Rus10Neo
ROLE_USER
ROLE_SONATA_EDITOR
ROLE_SONATA_READER
ROLE_SONATA_ADMIN
ROLE_FOOTBALL_PLAYER_MERGE
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_STAFF
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_BET_LINK_EDITOR
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_EDITOR
ROLE_ALL_TIPS_EDITOR
ROLE_INTELL_ADMIN_ADMIN_FOOTBALL_MEETING_TV_CHANNEL_ADMIN
ROLE_TIP_LOCALE_ADMIN
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_BET_LINK_ADMIN
ROLE_INTELL_ADMIN_ADMIN_IN_FORECAST_ADMIN
Добавить комментарий |
Bash-скрипты, часть 8: язык обработки данных awk
В прошлый раз мы говорили о потоковом редакторе sed и рассмотрели немало примеров обработки текста с его помощью. Sed способен решать многие задачи, но есть у него и ограничения. Иногда нужен более совершенный инструмент для обработки данных, нечто вроде языка программирования. Собственно говоря, такой инструмент — awk.
Утилита awk, или точнее GNU awk, в сравнении с sed, выводит обработку потоков данных на более высокий уровень. Благодаря awk в нашем распоряжении оказывается язык программирования, а не довольно скромный набор команд, отдаваемых редактору. С помощью языка программирования awk можно выполнять следующие действия:
- Объявлять переменные для хранения данных.
- Использовать арифметические и строковые операторы для работы с данными.
- Использовать структурные элементы и управляющие конструкции языка, такие, как оператор if-then и циклы, что позволяет реализовать сложные алгоритмы обработки данных.
- Создавать форматированные отчёты.
Особенности вызова awk
Схема вызова awk выглядит так:
$ awk options program file
Awk воспринимает поступающие к нему данные в виде набора записей. Записи представляют собой наборы полей. Упрощенно, если не учитывать возможности настройки awk и говорить о некоем вполне обычном тексте, строки которого разделены символами перевода строки, запись — это строка. Поле — это слово в строке.
Рассмотрим наиболее часто используемые ключи командной строки awk:
-F fs — позволяет указать символ-разделитель для полей в записи.
-f file — указывает имя файла, из которого нужно прочесть awk-скрипт.
-v var=value — позволяет объявить переменную и задать её значение по умолчанию, которое будет использовать awk.
-mf N — задаёт максимальное число полей для обработки в файле данных.
-mr N — задаёт максимальный размер записи в файле данных.
-W keyword — позволяет задать режим совместимости или уровень выдачи предупреждений awk.
Настоящая мощь awk скрывается в той части команды его вызова, которая помечена выше как program . Она указывает на файл awk-скрипта, написанный программистом и предназначенный для чтения данных, их обработки и вывода результатов.
Чтение awk-скриптов из командной строки
Скрипты awk, которые можно писать прямо в командной строке, оформляются в виде текстов команд, заключённых в фигурные скобки. Кроме того, так как awk предполагает, что скрипт представляет собой текстовую строку, его нужно заключить в одинарные кавычки:
Запустим эту команду… И ничего не произойдёт Дело тут в том, что мы, при вызове awk, не указали файл с данными. В подобной ситуации awk ожидает поступления данных из STDIN. Поэтому выполнение такой команды не приводит к немедленно наблюдаемым эффектам, но это не значит, что awk не работает — он ждёт входных данных из STDIN .
Если теперь ввести что-нибудь в консоль и нажать Enter , awk обработает введённые данные с помощью скрипта, заданного при его запуске. Awk обрабатывает текст из потока ввода построчно, этим он похож на sed. В нашем случае awk ничего не делает с данными, он лишь, в ответ на каждую новую полученную им строку, выводит на экран текст, заданный в команде print .
Первый запуск awk, вывод на экран заданного текста
Что бы мы ни ввели, результат в данном случае будет одним и тем же — вывод текста.
Для того, чтобы завершить работу awk, нужно передать ему символ конца файла (EOF, End-of-File). Сделать это можно, воспользовавшись сочетанием клавиш CTRL + D .
Неудивительно, если этот первый пример показался вам не особо впечатляющим. Однако, самое интересное — впереди.
Позиционные переменные, хранящие данные полей
Одна из основных функций awk заключается в возможности манипулировать данными в текстовых файлах. Делается это путём автоматического назначения переменной каждому элементу в строке. По умолчанию awk назначает следующие переменные каждому полю данных, обнаруженному им в записи:
- $0 — представляет всю строку текста (запись).
- $1 — первое поле.
- $2 — второе поле.
- $n — n-ное поле.
Рассмотрим использование этих переменных на простом примере. А именно, обработаем файл, в котором содержится несколько строк (этот файл показан на рисунке ниже) с помощью такой команды:
$ awk '' myfile
Вывод в консоль первого поля каждой строки
Здесь использована переменная $1 , которая позволяет получить доступ к первому полю каждой строки и вывести его на экран.
Иногда в некоторых файлах в качестве разделителей полей используется что-то, отличающееся от пробелов или символов табуляции. Выше мы упоминали ключ awk -F , который позволяет задать необходимый для обработки конкретного файла разделитель:
$ awk -F: '' /etc/passwd
Указание символа-разделителя при вызове awk
Эта команда выводит первые элементы строк, содержащихся в файле /etc/passwd . Так как в этом файле в качестве разделителей используются двоеточия, именно этот символ был передан awk после ключа -F .
Использование нескольких команд
Вызов awk с одной командой обработки текста — подход очень ограниченный. Awk позволяет обрабатывать данные с использованием многострочных скриптов. Для того, чтобы передать awk многострочную команду при вызове его из консоли, нужно разделить её части точкой с запятой:
$ echo "My name is Tom" | awk ''
Вызов awk из командной строки с передачей ему многострочного скрипта
В данном примере первая команда записывает новое значение в переменную $4 , а вторая выводит на экран всю строку.
Чтение скрипта awk из файла
Awk позволяет хранить скрипты в файлах и ссылаться на них, используя ключ -f . Подготовим файл testfile , в который запишем следующее:
Вызовем awk, указав этот файл в качестве источника команд:
$ awk -F: -f testfile /etc/passwd
Вызов awk с указанием файла скрипта
Тут мы выводим из файла /etc/passwd имена пользователей, которые попадают в переменную $1 , и их домашние директории, которые попадают в $6 . Обратите внимание на то, что файл скрипта задают с помощью ключа -f , а разделитель полей, двоеточие в нашем случае, с помощью ключа -F .
В файле скрипта может содержаться множество команд, при этом каждую из них достаточно записывать с новой строки, ставить после каждой точку с запятой не требуется.
Вот как это может выглядеть:
Тут мы храним текст, используемый при выводе данных, полученных из каждой строки обрабатываемого файла, в переменной, и используем эту переменную в команде print . Если воспроизвести предыдущий пример, записав этот код в файл testfile , выведено будет то же самое.
Выполнение команд до начала обработки данных
Иногда нужно выполнить какие-то действия до того, как скрипт начнёт обработку записей из входного потока. Например — создать шапку отчёта или что-то подобное.
Для этого можно воспользоваться ключевым словом BEGIN . Команды, которые следуют за BEGIN , будут исполнены до начала обработки данных. В простейшем виде это выглядит так:
$ awk 'BEGIN '
А вот — немного более сложный пример:
$ awk 'BEGIN ' myfile
Выполнение команд до начала обработки данных
Сначала awk исполняет блок BEGIN , после чего выполняется обработка данных. Будьте внимательны с одинарными кавычками, используя подобные конструкции в командной строке. Обратите внимание на то, что и блок BEGIN , и команды обработки потока, являются в представлении awk одной строкой. Первая одинарная кавычка, ограничивающая эту строку, стоит перед BEGIN . Вторая — после закрывающей фигурной скобки команды обработки данных.
Выполнение команд после окончания обработки данных
Ключевое слово END позволяет задавать команды, которые надо выполнить после окончания обработки данных:
$ awk 'BEGIN END ' myfile
Результаты работы скрипта, в котором имеются блоки BEGIN и END
После завершения вывода содержимого файла, awk выполняет команды блока END . Это полезная возможность, с её помощью, например, можно сформировать подвал отчёта. Теперь напишем скрипт следующего содержания и сохраним его в файле myscript :
BEGIN < print "The latest list of users and shells" print " UserName \t HomePath" print "-------- \t -------" FS=":" > < print $1 " \t " $6 >END
Тут, в блоке BEGIN , создаётся заголовок табличного отчёта. В этом же разделе мы указываем символ-разделитель. После окончания обработки файла, благодаря блоку END , система сообщит нам о том, что работа окончена.
$ awk -f myscript /etc/passwd
Обработка файла /etc/passwd с помощью awk-скрипта
Всё, о чём мы говорили выше — лишь малая часть возможностей awk. Продолжим освоение этого полезного инструмента.
Встроенные переменные: настройка процесса обработки данных
Утилита awk использует встроенные переменные, которые позволяют настраивать процесс обработки данных и дают доступ как к обрабатываемым данным, так и к некоторым сведениям о них.
Мы уже рассматривали позиционные переменные — $1 , $2 , $3 , которые позволяют извлекать значения полей, работали мы и с некоторыми другими переменными. На самом деле, их довольно много. Вот некоторые из наиболее часто используемых:
FIELDWIDTHS — разделённый пробелами список чисел, определяющий точную ширину каждого поля данных с учётом разделителей полей.
FS — уже знакомая вам переменная, позволяющая задавать символ-разделитель полей.
RS — переменная, которая позволяет задавать символ-разделитель записей.
OFS — разделитель полей на выводе awk-скрипта.
ORS — разделитель записей на выводе awk-скрипта.
По умолчанию переменная OFS настроена на использование пробела. Её можно установить так, как нужно для целей вывода данных:
$ awk 'BEGIN ' /etc/passwd
Установка разделителя полей выходного потока
Переменная FIELDWIDTHS позволяет читать записи без использования символа-разделителя полей.
В некоторых случаях, вместо использования разделителя полей, данные в пределах записей расположены в колонках постоянной ширины. В подобных случаях необходимо задать переменную FIELDWIDTHS таким образом, чтобы её содержимое соответствовало особенностям представления данных.
При установленной переменной FIELDWIDTHS awk будет игнорировать переменную FS и находить поля данных в соответствии со сведениями об их ширине, заданными в FIELDWIDTHS .
Предположим, имеется файл testfile , содержащий такие данные:
1235.9652147.91 927-8.365217.27 36257.8157492.5
Известно, что внутренняя организация этих данных соответствует шаблону 3-5-2-5, то есть, первое поле имеет ширину 3 символа, второе — 5, и так далее. Вот скрипт, который позволит разобрать такие записи:
$ awk 'BEGIN' testfile
Использование переменной FIELDWIDTHS
Посмотрим на то, что выведет скрипт. Данные разобраны с учётом значения переменной FIELDWIDTHS , в результате числа и другие символы в строках разбиты в соответствии с заданной шириной полей.
Переменные RS и ORS задают порядок обработки записей. По умолчанию RS и ORS установлены на символ перевода строки. Это означает, что awk воспринимает каждую новую строку текста как новую запись и выводит каждую запись с новой строки.
Иногда случается так, что поля в потоке данных распределены по нескольким строкам. Например, пусть имеется такой файл с именем addresses :
Person Name 123 High Street (222) 466-1234 Another person 487 High Street (523) 643-8754
Если попытаться прочесть эти данные при условии, что FS и RS установлены в значения по умолчанию, awk сочтёт каждую новую строку отдельной записью и выделит поля, опираясь на пробелы. Это не то, что нам в данном случае нужно.
Для того, чтобы решить эту проблему, в FS надо записать символ перевода строки. Это укажет awk на то, что каждая строка в потоке данных является отдельным полем.
Кроме того, в данном примере понадобится записать в переменную RS пустую строку. Обратите внимание на то, что в файле блоки данных о разных людях разделены пустой строкой. В результате awk будет считать пустые строки разделителями записей. Вот как всё это сделать:
$ awk 'BEGIN ' addresses
Результаты настройки переменных RS и FS
Как видите, awk, благодаря таким настройкам переменных, воспринимает строки из файла как поля, а разделителями записей становятся пустые строки.
Встроенные переменные: сведения о данных и об окружении
Помимо встроенных переменных, о которых мы уже говорили, существуют и другие, которые предоставляют сведения о данных и об окружении, в котором работает awk:
ARGC — количество аргументов командной строки.
ARGV — массив с аргументами командной строки.
ARGIND — индекс текущего обрабатываемого файла в массиве ARGV .
ENVIRON — ассоциативный массив с переменными окружения и их значениями.
ERRNO — код системной ошибки, которая может возникнуть при чтении или закрытии входных файлов.
FILENAME — имя входного файла с данными.
FNR — номер текущей записи в файле данных.
IGNORECASE — если эта переменная установлена в ненулевое значение, при обработке игнорируется регистр символов.
NF — общее число полей данных в текущей записи.
NR — общее число обработанных записей.
Переменные ARGC и ARGV позволяют работать с аргументами командной строки. При этом скрипт, переданный awk, не попадает в массив аргументов ARGV . Напишем такой скрипт:
$ awk 'BEGIN' myfile
После его запуска можно узнать, что общее число аргументов командной строки — 2, а под индексом 1 в массиве ARGV записано имя обрабатываемого файла. В элементе массива с индексом 0 в данном случае будет «awk».
Работа с параметрами командной строки
Переменная ENVIRON представляет собой ассоциативный массив с переменными среды. Опробуем её:
$ awk ' BEGIN< print ENVIRON["HOME"] print ENVIRON["PATH"] >'
Работа с переменными среды
Переменные среды можно использовать и без обращения к ENVIRON . Сделать это, например, можно так:
$ echo | awk -v home=$HOME ''
Работа с переменными среды без использования ENVIRON
Переменная NF позволяет обращаться к последнему полю данных в записи, не зная его точной позиции:
$ awk 'BEGIN ' /etc/passwd
Пример использования переменной NF
Эта переменная содержит числовой индекс последнего поля данных в записи. Обратиться к данному полю можно, поместив перед NF знак $ .
Переменные FNR и NR , хотя и могут показаться похожими, на самом деле различаются. Так, переменная FNR хранит число записей, обработанных в текущем файле. Переменная NR хранит общее число обработанных записей. Рассмотрим пару примеров, передав awk один и тот же файл дважды:
$ awk 'BEGIN