awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。

语法

awk [option] 'program' var=value file…
awk [option] -f programfile var=value file…
awk [option] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ...

awk是由模式和操作组成的。

模式可以是以下任意一个:

  • /正则表达式/:使用通配符的扩展集。
  • 关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试。
  • 模式匹配表达式:用运算符~(匹配)和!~(不匹配)。
  • BEGIN语句块、pattern语句块、END语句块:参见awk的工作原理

操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:

  • 变量或数组赋值
  • 输出命令
  • 内置函数
  • 控制流语句

awk基本结构

awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file

通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块3部分组成,这三个部分是可选的。任意一个部分都可以不出现在脚本中,脚本通常是被 单引号 或 双引号 中,例如:

awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename
awk "BEGIN{ i=0 } { i++ } END{ print i }" filename

awk的工作原理

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
  • 第一步:执行BEGIN{ commands }语句块中的语句;
  • 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
  • 第三步:当读至输入流末尾时,执行END{ commands }语句块。

BEGIN语句块 在awk开始从输入流中读取行 之前 被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。

END语句块 在awk从输入流中读取完所有的行 之后 即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。

pattern语句块 中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。

常见option常用参数,更多参数请查看手册

  • -F fs:fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:
  • -v var=value:赋值一个用户定义变量,将外部变量传递给awk
  • -f scripfile:从脚本文件中读取awk命令

变量

变量分为内置变量和自定义变量,每个变量前需加-v

常用内置变量,更多变量请查看手册

  • FS :输入字段分隔符,默认为空白字符
# cat << EOF >test
hello:world
linux:redhat:lalala:hahaha
along:love:youou
EOF
# cat << EOF >test1
1:2
1:2:3:4
1:2:3:4:5
EOF
# awk -v FS=':' '{print $1,$2}' test
  • OFS :输出字段分隔符,默认为空白字符
# awk -v FS=':' -v OFS='---' '{print $1,$2}' test
  • RS :输入记录分隔符,指定输入时的换行符,原换行符仍有效
# awk -v RS=':' '{print $1,$2}' test
  • ORS :输出记录分隔符,输出时用指定符号代替换行符
# awk -v FS=':' -v ORS='---' '{print $1,$2}' test
  • NF :字段数量,共有多少字段, $NF引用最后一列,$(NF-1)引用倒数第2列
# awk -F: '{print NF}' test
# awk -F: '{print $(NF-1)}' test
  • NR :行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始
# awk '{print NR}' test test1
# awk END'{print NR}' test test1
  • FNR :各文件分别计数, 行号,后跟一个文件和NR一样,跟多个文件,第二个文件行号从1开始
# awk '{print FNR}' test test1
  • FILENAME :当前文件名
# awk '{print FILENAME}' test
  • ARGC :命令行参数的个数
# awk 'BEGIN {print ARGC}' test test1
  • ARGV :数组,保存的是命令行所给定的各参数,查看参数
# awk 'BEGIN {print ARGV[0]}' test test1
# awk 'BEGIN {print ARGV[1]}' test test1
# awk 'BEGIN {print ARGV[2]}' test test1

自定义变量

(1)-v定义

定义变量,执行动作print
# awk -v name="along" -F: '{print name":"$0}' test
执行动作后定义变量
# awk -F: '{print name":"$0;name="along"}' test

(2)在文件中定义

# cat > awk.txt << EOF 
{name="along";print name,$0}
EOF
# awk -F: -f awk.txt test

操作符

  • 算术操作符:
    • x+y, x-y, x*y, x/y, x^y, x%y
    • -x: 转换为负数
    • +x: 转换为数值
  • 字符串操作符:没有符号的操作符,字符串连接
  • 赋值操作符:
    • =, +=, -=, *=, /=, %=, ^=
    • ++, --
  • 比较操作符:
    • ==, !=, >, >=, <, <=
  • 模式匹配符:~ :左边是否和右边匹配包含 !~ :是否不匹配
  • 逻辑操作符:与&& ,或|| ,非!
  • 函数调用: function_name(argu1, argu2, ...)
  • 条件表达式(三目表达式):selector?if-true-expression:if-false-expression
    • 注释:先判断selector,如果符合执行 ? 后的操作;否则执行 : 后的操作

实例

# df -h |awk -F: '$0 ~ /^\/dev/'
# df -h |awk '$0 ~ /^\/dev/{print $(NF-1)"---"$1}'
# df -h |awk '$0 ~ /^\/dev/{print $(NF-1)"---"$1}' |awk -F% '$1 > 40'
# awk -F: '$3>=0 && $3<=1000 {print $1,$3}' /etc/passwd
# awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
# awk -F: '!($3==0) {print $1}' /etc/passwd
# awk -F: '!($0 ~ /bash$/) {print $1,$3}' /etc/passwd
# awk -F: '{$3 >= 1000?usertype="common user":usertype="sysadmin user";print usertype,$1,$3}' /etc/passwd

匹配

根据pattern 条件,过滤匹配的行,再做处理

  • 如果未指定:空模式,匹配每一行
  • /regular expression/ :仅处理能够模式匹配到的行,正则,需要用/ / 括起来
  • relational expression:关系表达式,结果为“真”才会被处理
    • 真:结果为非0值,非空字符串
    • 假:结果为空字符串或0值
  • line ranges:行范围
    • startline(起始行),endline(结束行):/pat1/,/pat2/ 不支持直接给出数字,可以有多段,中间可以有间隔
  • BEGIN/END 模式
    • BEGIN{}: 仅在开始处理文件中的文本之前执行一次
    • END{} :仅在文本处理完成之后执行

实例

# awk -F: '{print $1}' test
# awk -F: '/along/{print $1}' test
# awk -F: '0{print $1}' test
# awk -F: '/^h/,/^a/{print $1}' test
# awk -F: 'BEGIN{print "第一列"}{print $1} END{print "结束"}' test

判断语句

格式

if(condition){statement;…}[else statement]  双分支
if(condition1){statement1}else if(condition2){statement2}else{statement3}  多分支

# awk -F: '
{
if($3>10 && $3<1000)print $1,$3
}' /etc/passwd

# awk -F: '
{
if($NF=="/bin/bash") print $1,$NF
}' /etc/passwd

# awk -F: '
{
if($3>=1000) {printf "Common user: %s\n",$1} 
else{printf "root or Sysuser: %s\n",$1}}
' /etc/passwd

# awk '
BEGIN{ 
test=100;
if(test>90){print "very good"}
else if(test>60){ print "good"}
else{print "no pass"}
}'

循环语句

语法

while循环
while(condition){statement;…}

do-while循环
do{statement;…}while(condition)

for循环
for(expr1;expr2;expr3) {statement;…}
for(var in array) {for-body}

实例

# awk -F: '
/^along/
{i=1;while(i<=NF){print $i,length($i); i++}}
' test

# awk -F: '
{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}
' test

# awk '
BEGIN{
i=1;sum=0;while(i<=100){sum+=i;i++};print sum
}'

# awk '
BEGIN{
sum=0;i=1;do{sum+=i;i++}while(i<=100);print sum
}'

# awk -F: '
{for(i=1;i<=NF;i++) {print$i,length($i)}}
' test

# cat << EOF > test
xiaoming m 90
xiaohong f 93
xiaohei m 80
xiaofang f 99
EOF
# awk '
{m[$2]++;score[$2]+=$3}
END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}
' test

选择语句

语法

switch(expression) {case VALUE1 or /REGEXP/:statement1; case VALUE2 or /REGEXP2/: statement2;...; default: statementn}

实例

# awk -F: '
{
switch($1){
case /hello/:
print("corrent")
break
default:
print("error")
break
}
}
' test

awk数组

数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用0或空字符串来初始化,这根据上下文而定

数组定义

数字做数组索引

Array[1]="world"

字符串做数组索引

Array["first"]="world"

获取数组的值

{ for(item in array) {print array[item]}; }  
{ for(i=1;i<=len;i++) {print array[i]}; }         #Len是数组的长度

数组相关函数

length返回字符串以及数组长度,split进行分割字符串为数组,也会返回分割得到数组长度。

# awk 'BEGIN{info="it is a test";lens=split(info,tA," ");print length(tA),lens;}'
# awk 'BEGIN{info="it is a test";split(info,tA," ");print asort(tA);}'

输出数组

无序(因为数组是关联数组,默认是无序的。)

# awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'

有序

# awk 'BEGIN{info="it is a test";tlen=split(info,tA," ");for(k=1;k<=tlen;k++){print k,tA[k];}}'

键值操作

判断键值存在

# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}'  

删除键值

# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'                     

数值处理

rand():返回0和1之间一个随机数,需有个种子 srand(),没有种子,一直输出0.237788

实例

# awk 'BEGIN{print rand()}'
# awk 'BEGIN{srand();print rand()}'
# awk 'BEGIN{srand(); print int(rand()*100%50)+1}'

字符串处理

  • length([s]) :返回指定字符串的长度
  • sub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并将第一个匹配的内容替换为s
  • gsub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并全部替换为s 所表示的内容
  • plit(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…

实例

# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
# echo "2008:08:08 08:08:08" | awk '{split($0,i,":")}END{for(n in i){print n,i[n]}}'

awk自定义函数

语法

function name ( parameter, parameter, ... ) {
    statements
    return expression
}
定义函数()中需加参数,return返回值不是$?,是相当于echo输出

实例

# cat << EOF >test
function max(v1,v2) {
    v1>v2?var=v1:var=v2
    return var
}
BEGIN{a=3;b=2;print max(a,b)}
EOF
# awk -f test

awk调用bash命令

(1)system命令调用

# awk BEGIN'{system("hostname")}'
# awk 'BEGIN{name="along";system("echo "name)}'

(2)awk脚本

# cat << EOF > test
#!/bin/awk -f
{if(\$3 >= 1000)print \$1,\$3}
EOF
# chmod u+x test
# ./test -F: /etc/passwd

(3)向awk脚本传递参数

语法

awkfile var=value var2=value2... Inputfile

在BEGIN 过程 中不可用。直到 首行输入完成以后,变量才可用 。可以通过-v  参数,让awk 在执行BEGIN 之前得到变量的值。命令行中每一个指定的变量都需要一个-v

实例

# cat << EOF > test
#!/bin/awk -f
{if(\$3 >=min && \$3<=max)print \$1,\$3}
EOF
# chmod u+x test
# ./test -F: min=100 max=200 /etc/passwd