Журнал LinuxFormat - перейти на главную

LXF138:DrBrown2

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

Awk в при­ме­рах

Awk не обя­зан быть не­ук­лю­жим. По­учим­ся у од­но­строч­ни­ков-ве­те­ра­нов.

Я немно­го вол­ну­юсь, по­то­му что наш урок бу­дет про язык про­грам­ми­ро­вания. Мож­но ли это счи­тать «ад­минист­ри­ро­ванием»? Ну, во­об­ще-то лю­бой ува­жаю­щий се­бя си­с­ад­мин дол­жен об­ла­дать ба­зо­вы­ми знания­ми по про­грам­ми­ро­ванию. На­вер­ное, са­мый по­пу­ляр­ный язык про­грам­ми­ро­вания для ад­минист­ра­то­ров – сце­на­рии обо­лоч­ки, но ду­маю, что Awk идет за ним по пя­там. Во вся­ком слу­чае, в этом ме­ся­це я но­миниро­вал Awk на звание по­чет­но­го ин­ст­ру­мен­та администрирования.

Скрип­то­вый язык Awk на­зван по пер­вым бу­к­вам фа­ми­лий его соз­да­те­лей: Альф­ре­да Ахо [Alfred Aho], Пи­те­ра Вайн­бер­ге­ра [Peter Weinberger] и Брай­а­на Кернига­на [Brian Kernighan]. В ва­шем ди­ст­ри­бу­ти­ве ско­рее все­го бу­дет GAwk (‘G’ от ‘GNU’), пе­репи­сан­ная вер­сия ис­ход­но­го Awk – на­до ли го­во­рить, что рас­ши­ренн­ная. Awk пре­крас­но под­хо­дит для об­ра­бот­ки дан­ных, раз­би­тых на стро­ки, в ка­ж­дой из ко­то­рых есть по­ля, на­при­мер, дан­ные, раз­де­лен­ные на стро­ки и столб­цы. В Linux та­ких при­ме­ров мно­же­ство – фай­лы /etc/passwd, /etc/fstab, /etc/hosts и т. д. или вы­вод та­ких ко­манд, как df, ps -ef или ls -l, или ре­зуль­тат экс­пор­та элек­трон­ной таб­ли­цы в файл CSV (зна­чений, раз­де­лен­ных за­пя­ты­ми). По су­ти, Awk – свое­об­раз­ный эк­ви­ва­лент элек­трон­ной таб­ли­цы в команд­ной стро­ке. Ну, нечто вро­де это­го. Awk при­ме­ня­ет­ся для обоб­щения и пе­ре­фор­ма­ти­ро­вания вы­во­да ка­кой-то дру­гой про­грам­мы, пре­об­ра­зо­вания фор­ма­та вы­во­да про­грам­мы A во вход­ной фор­мат про­грам­мы B, про­вер­ки дан­ных... и да­же как язык про­грам­ми­ро­вания об­ще­го на­зна­чения.

Срав­не­ние Awk и C

Стран­но, ко­неч­но, со­пос­тав­лять Мо Мо­улем [Mo Mowlam] и Кар­лу Сар­ко­зи [Carla Sarkozy], но на Awk не­со­мнен­но по­вли­ял C. Их ариф ме­ти­че­ские опе­ра­ции и опе­ра­ции срав­не­ния очень по­хо­жи. В Awk есть вы­ра­же­ния if, else, for, break и continue, ко­то­рые оз­на­ча­ют то же са­мое, что в C. Но в Awk нет ста­ти­че­ской ти­пи­за­ции, как в C, и пе­ре­мен­ные не нуж­но объ­яв­лять до их ис­поль­зо­ва­ния; а есть встро­ен­ная про­вер­ка на со­от­вет­ст­вие ре­гу­ляр­ным вы­ра­же­ни­ям, от­сут­ст­вую­щая в C. И го­то­вый ме­ха­низм Awk «про­чи­тать стро­ку вво­да и раз­бить ее на по­ля» в C нуж­но реа­ли­зо­вы­вать са­мо­стоя­тель­но.

Раз­бор стро­ки за стро­кой

Пол­ное опи­сание Awk не уме­стит­ся в три страницы, и я по­ста­ра­юсь дать вам пред­став­ление об его воз­мож­но­стях на при­ме­рах. В клас­си­че­ском смыс­ле Unix, Awk – фильтр, по­сколь­ку счи­ты­ва­ет ис­ход­ный файл, ес­ли он ука­зан, или стан­дарт­ный ввод, ес­ли нет, и его мож­но ис­поль­зо­вать в ка­на­лах. Основ­ная за­да­ча Awk – стро­ка за стро­кой чи­тать вход­ные дан­ные и раз­би­вать ка­ж­дую стро­ку на по­ля.

В про­грам­ме на Awk доступ к по­лям мож­но по­лу­чить с по­мо­щью обо­зна­чений $1, $2 и т. д. Обо­зна­чение $0 ссыла­ет­ся на всю стро­ку. Для это­го не нуж­но пи­сать ни строч­ки ко­да – это часть внут­реннего ме­ханиз­ма Awk. Од­но из про­стей­ших при­менений Awk – фильт­ро­вать и пе­ре­фор­ма­ти­ро­вать вы­вод дру­гих ко­манд. Вот при­мер пе­ре­на­прав­ления вы­во­да коман­ды df в Awk:

$ df | awk ‘ /^\/dev/ { print $1 “: “ $5 }’
/dev/sda6: 56%
/dev/sdb1: 10%
/dev/sda3: 40%

Здесь мы ве­ле­ли Awk вы­брать стро­ки, на­чи­наю­щие­ся с /dev, и вы­вести их пер­вое и пя­тое по­ля.


Бо­лее длин­ные про­грам­мы удобнее по­ме­щать в от­дель­ный файл. В сле­дую­щем при­ме­ре мы от­сканиру­ем файл па­ро­лей в по­исках наи­боль­ше­го иден­ти­фи­ка­то­ра поль­зо­ва­те­ля. Вот про­грам­ма – я на­звал ее maxuid.awk:

<sourcal lang=awk>

BEGIN { FS = “:” ; maxuid = 0 }
$3 > maxuid { maxuid = $3 ; maxname = $1 }
END { print maxname “: “ maxuid }

</source>

В ко­манд­ной стро­ке ее мож­но за­пус­тить, ука­зав файл скрип­та с клю­чом -f:

$ awk -f maxuid.Awk /etc/passwd
nobody: 65534

Раз­бе­рем эту про­грам­му. Как и все сце­на­рии Awk, она со­сто­ит из пар «шаб­лон–дей­ствие». Шаб­ло­ны вы­би­ра­ют стро­ки, над ко­то­ры­ми нуж­но вы­полнить дей­ствия, а дей­ствия оп­ре­де­ля­ют, что с ними де­лать. Осо­бые шаб­ло­ны BEGIN и END по­зво­ля­ют за­дать дей­ствия, вы­пол­няе­мые пе­ред об­ра­бот­кой пер­вой стро­ки или по­сле об­ра­бот­ки по­следней.

На­ча­ло, се­ре­ди­на и конец

Шаб­ло­ны BEGIN обыч­но ис­поль­зу­ют­ся для инициа­ли­за­ции пе­ре­мен­ных. В дан­ном слу­чае, мы уста­нав­ли­ва­ем пе­ре­мен­ную раз­де­ли­те­ля по­ля FS в :, по­то­му что имен­но так раз­де­ле­ны по­ля в фай­ле па­ро­лей. (По умол­чанию в ка­че­стве раз­де­ли­те­ля ис­поль­зу­ют­ся про­бе­лы.) Шаб­лон END часто ис­поль­зу­ет­ся для вы­во­да ре­зуль­та­тов, как мы и сде­ла­ли в этом при­ме­ре. Сред­няя стро­ка вы­пол­ня­ет об­ра­бот­ку. Она го­во­рит: «Ес­ли третье по­ле боль­ше, чем са­мый боль­шой иден­ти­фи­ка­тор, ко­то­рый мы ви­де­ли до сих пор (maxuid), об­но­вить зна­чение maxuid и за­помнить свя­зан­ное с ним имя поль­зо­ва­те­ля».

Но есть и бо­лее про­стой спо­соб за­пустить про­грам­му. Ес­ли до­ба­вить в на­ча­ло скрип­та ком­би­на­цию сим­во­лов #! и сде­лать его ис­пол­няе­мым, мож­но за­пустить его как коман­ду, точ­но так же, как сце­на­рий обо­лоч­ки. Тогда наш скрипт бу­дет вы­гля­деть так:

 #!/usr/bin/awk -f
 BEGIN { FS =:; maxuid = 0 }
 $3 > maxuid { maxuid = $3 ; maxname = $1 }
 END { print maxname “: “ maxuid }

Пра­ва на вы­пол­не­ние мож­но вы­дать та­ким об­ра­зом:

$ chmod u+x maxuid.Awk

За­тем мож­но об­ра­тить­ся к про­грам­ме по име­ни, как к ко­ман­де:

$ ./maxuid.awk /etc/passwd
nobody: 65534

Шаб­лон или дей­ст­вие в вы­ра­же­нии Awk не­обя­за­тель­ны. Ес­ли нет шаб­ло­на, дей­ст­вие вы­пол­ня­ет­ся над ка­ж­дой стро­кой, а ес­ли не ука­за­но дей­ст­вие, Awk про­сто вы­во­дит стро­ку.

В оче­ред­ном при­ме­ре есть шаб­лон, но нет дей­ст­вия. Он по­ка­зы­ва­ет все учет­ные за­пи­си в /etc/passwd с UID 1000 или вы­ше:

$ awk -F: ‘$3 >= 1000’ /etc/passwd

А в этом при­ме­ре есть дей­ст­вие, но нет шаб­ло­на – он про­сто вы­во­дит все име­на поль­зо­ва­те­лей из /etc/passwd:

$ awk -F: ‘{ print $1 }’ /etc/passwd

Об­ра­ти­те внимание на оп­цию -F: она слу­жит для ука­зания раз­де­ли­те­ля по­лей. Это аль­тер­на­ти­ва яв­но­му при­своению раз­де­ли­те­ля пе­ре­мен­ной FS из на­ших пре­ды­ду­щих при­ме­ров.

Ча­стью сво­ей мо­щи Awk обя­зан ог­ром­но­му ко­ли­че­ству ве­щей, до­пусти­мых в ка­че­стве шаб­ло­на, т. е. воз­мож­но­сти вы­брать стро­ки, над ко­то­ры­ми бу­дет вы­полнено дей­ствие.

В од­ну стро­ку

Как вы уви­ди­те, про­грам­мам на Awk не обя­за­тель­но быть длин­ны­ми, что­бы быть по­лез­ны­ми. Я бы пред­по­ло­жил (это толь­ко до­гад­ка), что в среднеста­ти­сти­че­ской про­грам­ме не боль­ше 10 строк. Вот еще несколь­ко од­но­строч­ных про­грамм. В ка­ж­дом слу­чае при­ве­де­на имен­но про­грам­ма, а не вся команд­ная стро­ка.

Вы­вес­ти ка­ж­дую де­ся­тую стро­ку:

NR%10 == 0

Про­ну­ме­ро­вать стро­ки в вы­во­де:

{ print NR, $0 }

Вы­вес­ти вход­ные стро­ки в об­рат­ном по­ряд­ке:

{ s = $0 “\n” s } END { print s }

Вы­вес­ти стро­ки, вы­ров­нен­ные по цен­тру (по столб­цу 40):

{ printf “%” int(40+length($0)/2) “s\n”, $0 }

Вы­вес­ти не­пус­тые стро­ки:

NF

Да, это за­кон­чен­ная про­грам­ма! Воз­мож­но, здесь нуж­но по­яснение. NF (чис­ло по­лей) – встро­ен­ная пе­ре­мен­ная Awk, ко­то­рая об­нов­ля­ет­ся при ка­ж­дой стро­ке. Ес­ли ее ис­поль­зо­вать как шаб­лон (что мы и сде­ла­ли), то дей­ствие по умол­чанию (вы­вести стро­ку) вы­полнит­ся, ес­ли NF рав­на TRUE (не ноль).

Биб­лио­те­ка функ­ций

В Awk есть ма­лень­кая, но по­лез­ная встро­ен­ная биб­лио­те­ка функ­ций; при необ­хо­ди­мо­сти мож­но оп­ре­де­лить и свои соб­ствен­ные. Как и сле­до­ва­ло ожи­дать, есть мас­са функ­ций для ра­бо­ты со стро­ка­ми. Неко­то­рые из них при­ве­де­ны в таб­ли­це ниже. Так­же есть на­бор ма­те­ма­ти­че­ских функ­ций, вклю­чая sin(), cos(), exp(), sqrt() и rand(); по­след­няя пред­став­ля­ет со­бой генера­тор слу­чай­ных чи­сел.

Вот еще несколь­ко про­грамм в од­ну стро­ку, ис­поль­зую­щих встро­ен­ные функ­ции:

Вы­вес­ти стро­ки, ко­то­рые длин­нее 80 сим­во­лов:

length($0) > 80

Этот при­мер ме­ня­ет TTY на TERMINAL, но толь­ко в пер­вой стро­ке. Фраг­мент NR!=1 в кон­це – вто­рой шаб­лон, бла­го­да­ря ко­то­ро­му ос­таль­ные стро­ки вы­во­дят­ся без из­ме­не­ний:

NR==1 { gsub(“TTY”, “TERMINAL”); print } NR!=1

Обыч­ное по­ве­де­ние Awk – счи­тать ка­ж­дую вход­ную стро­ку от­дель­ной за­пи­сью, но ино­гда за­пи­си бы­ва­ют и в не­сколь­ко строк. На­при­мер, в сле­дую­щем фай­ле со­дер­жат­ся две за­пи­си по три стро­ки (имя, род­ной го­род, имя же­ны), раз­де­лен­ные про­бе­лами:

Homer Simpson
Springfield
Marge
Fred Flintstone
Bedrock
Wilma

Что­бы об­ра­бо­тать эти дан­ные, нуж­но из­ме­нить и раз­де­ли­тель по­лей (FS), и раз­де­ли­тель за­пи­сей (RS) в Awk. На­при­мер, сле­дую­щая про­грам­ма по­ка­зы­ва­ет жен всех жи­те­лей Бед­ро­ка. Фо­кус в том, что раз­де­ли­тель строк нуж­но ус­та­но­вить в null (пус­тая стро­ка):

 BEGIN { RS = “” ; FS = “\n” }
 $2 == “Bedrock” { print $3 }

Идем впе­ред

Вот при­мер под­лин­нее. Вы­вод ко­ман­ды ps -ef со­сто­ит из строк сле­дую­ще­го фор­ма­та:

chris 3014 1 0 Aug17 ? 00:01:19 gedit

Седь­мое по­ле со­дер­жит вре­мя ра­бо­ты про­цес­са в фор­ма­те ЧЧ:ММ:СС, и на­ша за­да­ча – пре­об­ра­зо­вать его в це­лое чис­ло се­кунд. Для это­го его нуж­но раз­бить на три по­ля и сно­ва со­брать их, вы­пол­нив со­от­вет­ст­вую­щие ариф­ме­ти­че­ские дей­ст­вия. Вот про­грам­ма на Awk: seconds.awk. Она со­дер­жит един­ст­вен­ное дей­ст­вие без шаб­ло­на (по­это­му оно при­ме­ня­ет­ся к ка­ж­дой стро­ке):


 { split($7, hms,:)
 secs = (hms[1] * 3600) + (hms[2] * 60) + hms[3]
 printf%6d %5d\n”, $2, secs
 }

Про­грам­му мож­но вы­звать та­ким об­ра­зом:

$ ps -ef | awk -f seconds.awk

Ни один из при­ве­ден­ных при­ме­ров не про­де­мон­ст­ри­ро­вал всей мо­щи Awk. Это пол­но­цен­ный язык со все­ми необ­хо­ди­мы­ми воз­мож­но­стя­ми – пе­ре­мен­ны­ми, мас­си­ва­ми, ариф­ме­ти­кой, цик­ла­ми, ветв­лением и встро­ен­ной биб­лио­те­кой функ­ций. Его на­бор опе­ра­то­ров и син­так­сис его цик­лов и ветв­лений на­по­ми­на­ют C, но без то­чек с за­пя­той.

Что­бы про­де­мон­ст­ри­ро­вать несколь­ко боль­шие его воз­мож­но­сти, на­пи­шем про­грам­му, ко­то­рая об­ра­ба­ты­ва­ет вы­вод коман­ды ps, пе­ча­тая ин­фор­ма­цию об об­щем по­треб­лении па­мя­ти ка­ж­дым поль­зо­ва­те­лем, у ко­то­ро­го есть ак­тив­ные про­цес­сы.

Как все­гда с Awk, пре­ж­де все­го нуж­но ра­зо­брать­ся, как ор­ганизо­ва­ны дан­ные. Вот несколь­ко строк вы­во­да ps:

$ ps aux
USER	 PID	 %CPU	 %MEM	 VSZ	 RSS	 TTY	 STAT START	 TIME	 COMMAND
root	 1	 0.0	 0.0	 23820	 2012	 ?	 Ss	12:21	 0:00	 /sbin/init
postfix 2012	 0.0	 0.0	 39420	 2224	 ?	 S	12:22	 0:00	 qmgr -l -t fifo -u
chris	 2113	 0.0	 0.3	 202092	 13080	 ?	 S	12:23	 0:01	 update-notifier
chris	 2182	 0.2	 1.4	 724972	 57568	 ?	 Sl	12:35	 0:20	 evolution

Здесь нас ин­те­ре­су­ет пер­вое по­ле (имя вла­дель­ца) и пя­тое по­ле (VSZ), по­ка­зы­ваю­щее объ­ем вир­ту­аль­ной па­мя­ти про­цес­са. Идея со­сто­ит в том, что­бы сло­жить все зна­че­ния VSZ для поль­зо­ва­те­лей chris, root и про­чих, и та­ким об­ра­зом по­лу­чить сум­му для ка­ж­до­го поль­зо­ва­те­ля. Вот на­ша про­грам­ма totmem.awk:

 $1 != “USER” { count[$1]++; tot[$1] += $6 }
 END {
 for (user in tot)
 printf%8s: %4d %8d\n”, user, count[user], tot[user]
 }

Ее вы­вод вы­гля­дит так:

$ ps aux | awk -f totmem.awk
gdm: 1 26256
chris: 60 11502604
syslog: 1 126216
rtkit: 1 43636
daemon: 1 18880
postfix: 2 78680
nobody: 1 21424
avahi: 2 67976
root: 113 1769064

Основ­ной эле­мент этой про­грам­мы – два ас­со­циа­тив­ных мас­си­ва count и tot. В ка­че­стве ин­дек­са по этим мас­си­вам мы ис­поль­зу­ем имя поль­зо­ва­те­ля (со­хранен­ное в $1). По­сле об­ра­бот­ки всех строк count[«chris»] бу­дет со­дер­жать об­щее чис­ло про­цес­сов, вла­де­лец ко­то­рых – поль­зо­ва­тель chris, а tot[«chris»] – иско­мый объ­ем па­мя­ти. Мас­си­вы count[«root»] и tot[«root»] бу­дут со­дер­жать те же дан­ные для поль­зо­ва­те­ля root. Конеч­но, мы не зна­ем за­ранее, ка­кие име­на поль­зо­ва­те­лей по­па­дут­ся, но для цик­ла for все рав­но, по ка­ким зна­чениям про­хо­дить. Вы­ра­жение printf очень по­хо­же на вы­зов printf() – биб­лио­теч­ной функ­ции C. Она по­зво­ля­ет вы­вести дан­ные за­дан­ной ши­ри­ны, и чис­ла ак­ку­рат­но вы­равнива­ют­ся по столб­цам.

По­счи­тай­те сум­мы

Остав­лю вас с про­грам­мой немно­го в ином сти­ле – она не счи­ты­ва­ет ника­ких вход­ных дан­ных, а толь­ко вы­пол­ня­ет вы­чис­ления. Она вы­во­дит все про­стые чис­ла, мень­шие 1000. Здесь все де­ло в дей­ствии для шаб­ло­на BEGIN. Это не са­мый эф­фек­тив­ный ал­го­ритм, и Awk в лю­бом слу­чае – не луч­ший вы­бор для ра­бо­ты с чис­ла­ми. Но при­мер со­дер­жит несколь­ко цик­лов и ветв­лений и по­ка­зы­ва­ет, как оп­ре­де­лять соб­ствен­ные функ­ции.

 BEGIN {
 for (x = 3; x < 1000; x += 2)
 if (isprime(x)) print x
 }
 function isprime(a)
 {
 for (n = 2; n*n <= a; n++) {
 if (a%n == 0)
 return 0
 }
 return 1
 }

На­де­юсь, вам по­нра­ви­лось мое крат­кое вве­де­ние в Awk. Те­перь оче­редь за ва­ми – ус­пе­хов!

Где уз­нать боль­ше

Клас­си­че­ская кни­га «The Awk Programming Language» [Язык про­грам­ми­ро­ва­ния Awk] бы­ла на­пи­са­на соз­да­те­ля­ми язы­ка еще в 1988 го­ду, но все еще из­да­ет­ся. В книге опи­са­на ис­ход­ная вер­сия Awk, но это по-преж­не­му луч­шее ру­ко­во­дство по язы­ку. В ней все­го 200 стра­ниц, но она вы­хо­дит да­ле­ко за рам­ки про­сто­го опи­са­ния язы­ка – с гла­ва­ми о на­пи­са­нии син­так­си­че­ских ана­ли­за­то­ров для «язы­ков-ма­лю­ток» и об ал­го­рит­мах вро­де то­по­ло­ги­че­ской сор­ти­ров­ки. Так­же мож­но за­гру­зить пол­ное ру­ко­во­дство по Gawk (364 стра­ни­цы) в раз­лич­ных фор­ма­тах с сай­та http://www.gnu.org/software/gawk/manual. Есть еще сайт http://awk.info, где мож­но най­ти ис­чер­пы­ваю­щую под­бор­ку про­грамм на Awk – от од­но­строч­ных до дей­ст­ви­тель­но серь­ез­ных.

Персональные инструменты
купить
подписаться
Яндекс.Метрика