2024年4月29日发(作者:)

AWK

尹会生

--2010.9.6

注: 本文档中的代码和图片均来自《sed与awk(第二版)》

1

一 编写awk脚本

HELLO,WORLD

$ echo 'this line of data is ignored' > test

$ awk '{ print "Hello, world" }' test

Hello, world

test文件只包含一行,因此,print操作只执行一次。

$ cat test2

Hello, world

$ awk '{ print }' test2

Hello, world

print语句没有参数,只简单输出每个输入行。

$ awk ‘BEGIN {print “hello,World”}’

Hello,World

2

BEGIN模式不需要等待输入,它在第一个输入行读入之前执行。

awk程序设计模型

awk程序由所谓的主输入(main input)循环组成。一个循环称作一个例程。

awk允许你编写两个特殊的例程,他们在任何输入被读取前和所有输入都被读

取后执行。他们是与BEGIN和END规则相关的过程。BEGIN和END过程是可

选的。

模式匹配

# test for integer, string or empty line.

/[0-9]+/ { print "That is an integer" }

/[A-Za-z]+/ { print "This is a string" }

/^$/ { print "This is a blank line." }

3

一个特殊的例子:

$ awk -f awkscr

4T

That is an integer

This is a string

一行可以匹配一条或多条规则

程序脚本的注释

# 以#号开始的一行

记录和字段

awk假设它的输入是有结构的,而不是一串无规则的字符。默认它将每个输入

行作为一条记录,而将由空格或制表符分隔的单词作为字段。连续的多个空格

和/或制表符被作为一个分隔符。

John Robinson 666-555-1111

字段的引用和分离

awk允许使用字段操作符$来指定字段。$后面可以跟着一个数字或者一个变量。

$1表示第一个字段,$2表示第二个字段,$0表示整个输入记录。

$ awk '{ print $2, $1, $3 }' names

Robinson John 666-555-1111

4

可以使用计算值为整数的表达式来表示一个字段

$ echo a b c d | awk 'BEGIN { one = 1; two = 2 }

> { print $(one + two) }'

c

可以使用-F来改变字段分隔符

$ awk -F"t" '{ print $2 }' names

666-555-1111

$ awk -F"t+" '{ print $2 }' names

$ awk -F"[‘:t]" '{ print $2 }' names

任何3个字符之一都可以被解释为字段分隔符

也可以在脚本中指定域分隔符,通过系统变量FS来改变

BEGIN { FS = "," } # comma-delimited fields

{ print $1 "-" $2 }

使用匹配规则

/MA/ { print $1 ", " $6 }

5

为了避免假警报,可以使用更精确的匹配

$5 ~ /MA/ { print $1 ", " $6 }

还可以使用!来反转这个规则的意义

$5 !~ /MA/ { print $1 ", " $6 }

表达式

常量

分成两种:字符串型和数字型

字符串型在表达式中必须用引号括起来

字符串中可以使用转义序列,常用的转义序列有:

n 换行 t 水平制表符 r 回车

变量

x=1

x是变量的名字 =是一个赋值操作符 1是一个数字常量

注意: 变量区分大小写,所以x和X(大写)表示不同的变量

变量名只能由数字字母下划线组成,而且不能以数字开头

变量使用不区分类型,使用前不必初始化

z = "Hello"

6

z = "Hello" "World"

z = $1

以上几种都是合法的

常用算数操作符

Operato

r

+

-

*

/

%

x=1

给x赋值

y=x+1

计算x的值,使它加1,并将结果赋给变量y。

print y

打印y的值。

Description

Addition

Subtraction

Multiplication

Division

Modulo

我们可以将这3个语句减少为两个:

x=1

print x+1

7

常用赋值操作符

Operato

r

++

--

+=

-=

*=

/=

%=

^=

Description

Add 1 to variable.

Subtract 1 from variable.

Assign result of addition.

Assign result of subtraction.

Assign result of multiplication.

Assign result of division.

Assign result of modulo.

Assign result of exponentiation.

计算文件中空行的数目

# Count blank lines.

/^$/ {

print x += 1

}

x=x+1 x+=1 ++x x++

这几种有什么区别?

# Count blank lines.

/^$/ {

++x

}

END {

print x

8

}

计算学生的平均成绩

mona 70 77 85 83 70 89

john 85 92 78 94 88 91

andrea 89 90 85 94 90 95

jasper 84 88 80 92 84 82

dunce 64 80 60 60 61 62

ellis 90 98 89 96 96 92

$ awk -f grades

john 87.4

andrea 86

jasper 85.6

# average five grades

{ total = $2 + $3 + $4 + $5 + $6

avg = total / 5

print $1, avg }

9

也可以使用print $1, total / 5

系统变量

FS 定义字段分隔符,默认为一个空格

OFS 输出的字段分隔符,默认为一个空格

RS 记录分隔符,默认为一个换行符

ORS 输出的记录分隔符,默认为一个换行符

NR 行数

FNR 行数,多文件操作时会重新排序

NF 输出当前输入记录的编号(字段的个数)

FILENAME 文件名

例如:

$ awk –F: ‘OFS=”aaa”,ORS=”bbb” {print

/etc/passwd

$ print NR “.”,$1, avg

1. john 87.4

2. andrea 86

3. jasper 85.6

1

0

NR,FNR,NF,FILENAME}’

处理多行记录

John Robinson

Koren Inc.

978 Commonwealth Ave.

Boston

MA 01760

696-0987

# - print first and last fields

# $1 = name; $NF = phone number

BEGIN { FS = "n"; RS = " " }

{ print $1, $NF }

$ awk -f

John Robinson 696-0987

Phyllis Chapman 879-0900

Jeffrey Willis 914-636-0000

Alice Gold (707) 724-0000

Bill Gold 1-707-724-0000

关系操作符和布尔操作符

关系操作符

11

Operato

r

<

>

<=

>=

==

!=

~

!~

NF == 5

Description

Less than

Greater than

Less than or equal to

Greater than or equal to

Equal to

Not equal to

Matches

Does not match

NF(每个输入记录的字段数)的值和5相比较,如果结果为真,那

么就进行相应的处理,否则不进行处理。

$5 ~ /MA/ {print $1 “,”$6}

注意:关系操作符==和赋值操作符=是不同的

布尔操作符

Operato

r

||

&&

!

Descriptio

n

Logical OR

Logical AND

Logical NOT

NF == 6 && NR > 1

字段的数量必须等于6并且记录的编号必须大于1。

NR >1 && NF >=2 || $1 ~ /t/

(NR >1 && NF >=2 )|| $1 ~ /t/

!(NR > 1 && NF > 3)

获取文件的信息

1

2

$ ls -l |awk -f

BEGIN { print "BYTES", "t", "FILE" }

{

sum += $5

++filenum

print $5, "t", $8

}

END { print "Total: ", sum, "bytes (" filenum " files)" }

格式化打印

printf (

format-expression

[, arguments] )

Characte

r

c

d

f

s

x

Description

ASCII 字符

十进制整数

浮点格式

字符串

无符号十六进制

1

3

常用举例:

语法 %-

width

.

precision format-specifier

printf(" %d t %s n ", $5 , $8 )

printf("|%10s|n", "hello")

printf("|%-10s|n", "hello")

printf("%*.*fn", 5, 3, myvar)

右对齐

左对齐

宽度5 精度3 打印myvar

向脚本传递参数

awk 'script' var=value inputfile

$ var=root

$ awk –F: -v a=$var ‘$1==a {print}’ /etc/passwd

二 条件、循环和数组

条件语句

if (

expression

)

action1

[else

action2

]

1

4

例如:

if ( x ) print x

如果x是零,则print语句将不执行。如果x是一个非零值,将打印x的值。

if ( x == y ) print x

if ( x ~ /[yY](es)?/ ) print x

如果操作是由多个语句组成,要用一个大括号将操作括起来

if (

expression

) {

statement1

statement2

}

其他的例子:

if ( avg >= 65 )

grade = "Pass"

else

grade = "Fail"

if (avg >= 90) grade = "A"

else if (avg >= 80) grade = "B"

else if (avg >= 70) grade = "C"

else if (avg >= 60) grade = "D"

1

5

else grade = "F"

这种能够连续条件只有当一个条件表达式计算结果为真时才停止求值,这时将

跳过其他的条件。如果没有一个条件表达式的计算结果为真,将执行最后的

else部分。

条件操作符

expr

?

action1

:

action2

例如:

grade = (avg >= 60) ? "Pass" : "Fail"

循环

while循环

while循环语法:

while (

condition

)

action

例如:

i = 1

while ( i <= 4 ) {

print $i

++i

}

1

6

do循环

do

action

while (

condition

)

例如:

BEGIN {

do {

++x

print x

} while ( x <= 4 )

}

for循环

for (

set_counter

;

test_counter

;

increment_counter

)

action

例如:

从第一个字段到最后一个字段

for ( i = 1; i <= NF; i++ )

print $i

从最后一个字段到第一个字段

for ( i = NF; i >= 1; i-- )

1

7

print $i

用for实现

total = 0

for (i = 2; i <= NF; ++i)

#total = $2 + $3 + $4 + $5 + $6

#avg = total / 5

total += $i

avg = total / (NF - 1)

求阶乘

5!=5*4*3*2*1

fact = number

for (x = number - 1 ; x > 1; x--)

fact *= x

完整脚本 factorial

影响流控制的其他语句

影响控制流

break 退出循环

continue 终止当前的循环,并从循环的顶部开始一个新的循环

1

8

影响主输入循环

next 读入下一行,并返回脚本的顶部

exit 使主输入循环退出并将控制转移到END

数组

array

[

subscript

] =

value

在awk中不必指明数组的大小,只需要为数组制定标示符。

可以用数字作为数组的下标,也可以用字符来做数组的下标。

flavor[1] = "tulip"

student_avg[NR] = avg

关联数组

在awk中,所有的数组都是关联数组。关联数组的独特之处在于它的下标可以

是一个字符串或一个数值。

有一个特殊的循环语法可以访问关联数组的所有元素

for (

variable

in

array

)

do something with array

[

variable

]

例如:

1

9

for ( item in acro )

print item, acro[item]

一个计算学生成绩的例子:

mona 70 77 85 83 70 89

john 85 92 78 94 88 91

andrea 89 90 85 94 90 95

jasper 84 88 80 92 84 82

dunce 64 80 60 60 61 62

ellis 90 98 89 96 96 92

# -- average student grades and determine

# letter grade as well as class averages.

# $1 = student name; $2 - $NF = test scores.

# set output field separator to tab.

BEGIN { OFS = "t" }

# action applied to all input lines

{

2

0

# add up grades

total = 0

for (i = 2; i <= NF; ++i)

total += $i

# calculate average

avg = total / (NF - 1)

# assign student's average to element of array

student_avg[NR] = avg

# determine letter grade

if (avg >= 90) grade = "A"

else if (avg >= 80) grade = "B"

else if (avg >= 70) grade = "C"

else if (avg >= 60) grade = "D"

else grade = "F"

# increment counter for letter grade array

++class_grade[grade]

# print student name, average and letter grade

print $1, avg, grade

}

# print out class statistics

END {

# calculate class average

2

1

for (x = 1; x <= NR; x++)

class_avg_total += student_avg[x]

class_average = class_avg_total / NR

# determine how many above/below average

for (x = 1; x <= NR; x++)

if (student_avg[x] >= class_average)

++above_average

else

++below_average

# print results

print ""

print "Class Average: ", class_average

print "At or Above Average: ", above_average

print "Below Average: ", below_average

# print number of students per letter grade

for (letter_grade in class_grade)

print letter_grade ":", class_grade[letter_grade] | "sort"

}

删除数组

delete

array

[

subscript

]

2

2

多维数组

awk支持线性数组,不支持多为数组,但它为下标提供了一个语法来模拟引用

多维数组。

例如:

file_array[2, 4]

作为系统变量的数组

ARGV ENVIRON

命令行参数数组

ARGV

这是一个命令行参数的数组,不包括脚本本身和任何awk的选项。这个数

组中的元素个数由ARGC中获得。数组中第一个元素的下标是0(和awk中其

他数组不同,而和C一致),最后一个下标是ARGC-1.

例如:

# - print command-line parameters

BEGIN { for (x = 0; x < ARGC; ++x)

print ARGV[x]

print ARGC

}

2

3

$ awk -f 1234 "John Wayne" Westerns n=44 -

awk

1234

John Wayne

Westerns

n=44

-

6

第二个例子:

# - test command-line parameters

BEGIN {

for (x = 1; x < ARGC; ++x)

if ( ARGV[x] !~ /^[0-9]+$/ ) {

print ARGV[x], "is not an integer."

exit 1

}

}

环境变量数组

数组ENVIRON允许你访问环境变量

# - print environment variable

2

4

BEGIN {

for (env in ENVIRON)

print env "=" ENVIRON[env]

}

$ awk -f

DISPLAY=scribe:0.0

FRAME=Shell 3

LOGNAME=dale

MAIL=/usr/mail/dale

PATH=:/bin:/usr/bin:/usr/ucb:/work/bin:/mac/bin:.

TERM=mac2cs

HOME=/work/dale

SHELL=/bin/csh

TZ=PST8PDT

EDITOR=/usr/bin/vi

可以使用变量名作为数组的下标访问任意元素

ENVIRON["LOGNAME"]

也可以修改数组ENVIRON中的任意元素

ENVIRON["LOGNAME"] = "Tom"

三 函数

2

5

算数函数(部分)

Awk

Function

exp(

x

)

int(

x

)

sqrt(

x

)

rand()

srand(

x

)

Description

返回e的x次幂

返回x的整数部分的值

返回x的平方根

返回伪随机数r,其中0<=r<=1

建立rand()的新的种子数。如果没有指定种子数,就用当天的时间。

返回旧的种子值

print 100/3

print int(100/3)

随机数的生成

函数rand()生成一个在0和1之间的浮点型的伪随机数。函数srand()为

随机数发生器设置一个种子数。如果调用srand()时没有参数,它将用当前

的时间来生成一个种子数。

# -- test random number generation

BEGIN {

print rand()

print rand()

srand()

print rand()

print rand()

}

2

6

$ awk -f

0.513871

0.175726

0.760277

0.263863

$ awk -f

0.513871

0.175726

0.787988

0.305033

随机数的两个例子:

例子一:

awk -v TOPNUM=$1 '

# pick1 - pick one random number out of y

# main routine

BEGIN {

# seed random number using time of day

srand()

# get a random number

select = 1 + int(rand() * TOPNUM)

2

7

# print pick

print select

}'

Statement

print r = rand()

print r * TOPNUM

print int(r * TOPNUM)

print 1 + int(r * TOPNUM)

$ pick1 100

83

Result

0.46731

5

14.0195

14

15

例子二:

awk -v NUM=$1 -v TOPNUM=$2 '

# lotto - pick x random numbers out of y

# main routine

BEGIN {

# test command line args; NUM = $1, how many numbers to pick

# TOPNUM = $2, last number in series

if (NUM <= 0)

NUM = 6

2

8

if (TOPNUM <= 0)

TOPNUM = 30

# print "Pick x of y"

printf("Pick %d of %dn", NUM, TOPNUM)

# seed random number using time and date; do this once

srand()

# loop until we have NUM selections

for (j = 1; j <= NUM; ++j) {

# loop to find a not-yet-seen selection

do {

select = 1 + int(rand() * TOPNUM)

} while (select in pick)

pick[select] = select

}

# loop through array and print picks.

for (j in pick)

printf("%s ", pick[j])

printf("n")

}'

$ lotto 7 35

2

9

Pick 7 of 35

5 21 9 30 29 20 2

另,如果需要对数组下标排序可以

# create a numerically indexed array for sorting

i = 1

for (j in pick)

sortedpick[i++] = pick[j]

字符串函数

gsub(

r

,

s

,

t

)

index(

s

,

t

)

length(

s

)

match(s,r)

sub(

r

,

s

,

t

)

tolower(

s

)

toupper(

s

)

自定义函数

function

name

(

parameter-list

) {

在字符串t中用字符串s替换和正则表达式r匹配的所有字符串。

返回替换的个数。

返回子串t在字符串s中的位置。

返回字符串s的长度。

如果正则表达式r在s中出现,则返回出现的起始位置;如果在s

中没发现r,则返回0

在字符串t中用字符串s替换和正则表达式r匹配的首次匹配。成

功返回1,否则返回0。

将字符串s的所有大写字符转换为小写

将字符串s的所有小写字符转换为大写

statements

}

3

0

四 底部抽屉

getline()函数

getline函数用于从输入中读取另一行。getline函数不近能读取正常的输入数据

流,而且也能处理来自文件和管道的输入。getline可能的返回值为:

1如果能够读取一行

0 如果到了文件末尾

-1 如果遇到错误

$ awk '/firstline/{getline; print $0}' filename

the second line

从文件中读取

while ( (getline < "filename") > 0 )

print

将输入赋给一个变量input

getline input

从管道读取输入

"who am i" | getline

3

1

system()函数

执行一个命令

BEGIN { if (system(“mkdir dale’”) != 0)

print “Command Failed” }

练习一

1 打印UID在30-40之间的用户名

2 打印第5-10行,输出用户名和行号

3 打印奇数行

4 打印偶数行

5 打印UID不等于GID的用户名

6 打印没有指定shell的用户名

7 打印1-1000以内能被7整除的数和包含7的数 $1~/

3

2

7/