一. 基本介绍
awk 是一门特殊的编程语言, 它非常适合处理一些任务(改变数据格式、验证数据的有效性、搜索特定的数据项、求和、打印报表等), 经常只需要一两行便可搞定。
一个 awk 程序由一系列的模式和动作组成, 这些模式与动作说明了在输入中搜索哪些数据, 以及当符合条件的数据被找到时, 应该执行什么操作。
awk 在输入文件集合中搜索与模式相匹配的输入行, 当找到一个匹配行时, 便会执行对应的动作。通过字符串, 数值, 字段, 变量和数组元素的比较操作, 再加上正则表达式, 利用这些组合, 一个模式可以用来选择输入行, 而动作可以对选中的行作任意的处理。
二. awk 的语法
1. 命令行格式
2. 程序基本结构
awk 命令中的程序格式就是一个或多个这样的格式组成的:pattern { action }
awk 的基本操作是在由输入行组成的序列中, 陆续地扫描每一行, 搜索可以被模式(pattern)匹配的行。
每匹配一个模式, 对应的动作 (action)(可能包含多个步骤) 就会执行 。
例子:
$3 == 19 { print $1 }
建立一个文本文件:
# cat awk_demo
1 Adam 18 Boston
2 Bob 17 Charlotte
3 Chris 17 Dallas
4 David 19 Houston
5 Ellis 20 Illinois
如果只有模式,则会打印匹配这个模式的一整行:$3 == 19
# awk '$3==19' awk_demo
4 David 19 Houston
如果只有动作,对于每一个输入行,动作都会执行:{ print $1 }
# awk '{ print $1 }' awk_demo
1
2
3
4
5
运行完整命令的效果如下所示:
# awk '$3==19 { print $1 }' awk_demo
4
3. 正则表达式
在模式匹配时使用,具体规则请看神奇的正则表达式_yspg_217的博客-CSDN博客
4. 模式(pattern)的格式
expression { statements }
每碰到一个使 expression 为真的输入行, statements 就执行。expression 为真指的是其值非零或非空
/regular expression/ { statements }
当碰到这样一个输入行时, statements 就执行: 输入行含有一段字符串, 而该字符串可以被正则匹配
compound expression { statements }
一个复合模式将表达式用 &&(AND), ||(OR), !(NOT), 以及括号组合起来; 当 compound pattern 为真时, statements 执行
pattern1, pattern2 { statements }
一个范围模式匹配多个输入行, 这些输入行从匹配 pattern1 的行开始, 到匹配 pattern2 的行结束 (包括这两行), 对这其中的每一行执行 statements
BEGIN { statements }
在输入被读取之前, statements 执行一次
END { statements}
当所有输入被读取完毕之后, statements 执行一次
5. 两种特殊的模式(BEGIN END)
BEGIN 与 END 这两个模式不匹配任何输入行。
当 awk 从输入读取数据之前, BEGIN 的语句开始执行; 当所有输入数据被读取完毕, END 的语句开始执行。于是, BEGIN 与 END 分别提供了一种控制初始化与收尾的方式。
BEGIN 与 END 不能与其他模式作组合。如果有多个 BEGIN, 与其关联的动作会按照它们在程序中出现的顺序执行, 这种行为对多个 END 同样适用。通常将 BEGIN 放在程序开头, 将 END 放在程序末尾。
BEGIN 与 END 是唯二的不能省略动作的模式。
6. 动作(action)的格式
有时候动作会非常简单: 一条单独 的打印语句或赋值语句。
在有些时候, 动作有可能是多条语句, 语句之间用换行符或分号分开。
参数类型:
常量:字符串,数值
变量:自己定义
内建变量:awk 系统原生的变量
字段变量:$1, $2, … , $NF(NF表示当前行的字段的数目)
7. 内建变量
首先介绍两个重要的概念,字段(Field)和记录(Record):
记录(Record):默认一行内容代表一个记录
字段(Field):一个记录包括多个字段
内建变量 | 意义 | 默认值 |
---|---|---|
ARGC | 命令行参数的个数 | 无 |
ARGV | 命令行参数数组 | 无 |
FILENAME | 当前输入文件名 | 无 |
FNR | 当前输入文件的记录个数 | 无 |
FS | 控制着输入行的字段分隔符 | " " |
NF | 当前记录的字段个数 | 无 |
NR | 到目前为止读取的记录数量 | 无 |
$0 | 当前行的全部内容 | 无 |
$n | 当前行第n个字段的内容 | 无 |
OFMT | 数值的输出格式 | "%.6g" |
OFS | 输出字段分隔符 | " " |
ORS | 输出记录分隔符 | "\n" |
RLENGTH | 被函数 match 匹配的字符串长度 | 无 |
RS | 控制着输入行记录的分隔符 | "\n" |
RSTART | 被函数 match 匹配的字符串的开始 | 无 |
SUBSEP | 下标分隔符 | "\034" |
# awk '{print ARGC}' awk_demo
2
2
2
2
2
# awk '{print ARGV[ARGC-2]}' awk_demo
awk
awk
awk
awk
awk
# awk '{print ARGV[ARGC-1]}' awk_demo
awk_demo
awk_demo
awk_demo
awk_demo
awk_demo
# awk '{print FILENAME}' awk_demo
awk_demo
awk_demo
awk_demo
awk_demo
awk_demo
# awk '{print FNR}' awk_demo
1
2
3
4
5
# awk -v FS=" " '{print $2}' awk_demo
Adam
Bob
Chris
David
Ellis
# awk '{print NF}' awk_demo
4
4
4
4
4
# awk '{print NR}' awk_demo
1
2
3
4
5
# awk 'BEGIN {OFS=":"} {print $1,$2}' awk_demo
1:Adam
2:Bob
3:Chris
4:David
5:Ellis
# awk 'BEGIN {OFS=":"} {print $1 $2}' awk_demo
1Adam
2Bob
3Chris
4David
5Ellis
# awk 'BEGIN {OFS=":"; ORS="\t"} {print $1,$2}' awk_demo
1:Adam 2:Bob 3:Chris 4:David 5:Ellis
# awk 'BEGIN {RS=" "} {print}' awk_demo
1
Adam
18
Boston
2
Bob
17
Charlotte
3
Chris
17
Dallas
4
David
19
Houston
5
Ellis
20
Illinois
8. 运算符与代数函数
运算符 | |
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
^或** | 乘方 |
++ | 加1 |
-- | 减1 |
+= | a+=b 等价于 a=a+b |
-= | a-=b 等价于 a=a-b |
*= | a*=b 等价于 a=a*b |
/= | a/=b 等价于 a=a/b |
%= | a%=b 等价于 a=a%b |
^= | a^=b 等价于 a=a^b |
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
== | 等于 |
!= | 不等于 |
~ | 匹配 |
!~ | 不匹配 |
|| | 逻辑或 |
&& | 逻辑与 |
! | 逻辑非 |
代数函数 | |
sin(x) | x的正弦 |
cos(x) | x的余弦 |
exp(x) | 自然对数的x次方 |
int(x) | x取整 |
log(x) | ln(x) |
sqrt(x) | x的开方 |
atan2(y,x) | arctan(y/x),取值范围是-pi~pi |
rand() | 伪随机数[0,1) |
srand() | x设置为新的种子,返回旧种子的值,如果没有x,就用当天时间 |
实例函数:
# cat awk_s.awk
NR==1{
print $1"加"$2 "=" $1+$2
print $1"减"$2"=" $1-$2
print $1"乘"$2"=" $1*$2
print $1"除以"$2"=" $1/$2
print $1"对"$2"取余=" $1%$2
print $1"的"$2"次方=" $1^$2
print $1"的自增=" $1++
print $1"的自减=" $1--
print "自然对数的"$1"次幂=" exp($1)
print $3"取整=" int($3)
print $1"的对数=" log($1)
print $1"的开方=" sqrt($1)
print $2/$1"的arctan值=" atan2($2,$1)
print "随机数=" rand()
print "旧的种子值=" srand($3)
}
NR>1{
print $1"的正弦=" sin($1)
print $1"的余弦=" cos($1)
}
示例文本文件:
# cat awk_demo2
2 1 3.14
0
15
30
45
60
90
执行结果:
# awk -f awk_s.awk awk_demo2
2加1=3
2减1=1
2乘1=2
2除以1=2
2对1取余=0
2的1次方=2
2的自增=2
3的自减=3
自然对数的2次幂=7.38906
3.14取整=3
2的对数=0.693147
2的开方=1.41421
0.5的arctan值=0.463648
随机数=0.237788
旧的种子值=1
0的正弦=0
0的余弦=1
15的正弦=0.650288
15的余弦=-0.759688
30的正弦=-0.988032
30的余弦=0.154251
45的正弦=0.850904
45的余弦=0.525322
60的正弦=-0.304811
60的余弦=-0.952413
90的正弦=0.893997
90的余弦=-0.448074
9. 输出方法(print printf)
重点介绍一下printf:
格式为:printf( format-expression [, arguments] )
format-expression 中的格式控制符:
fmt | $1 | printf("fmt",$1) |
---|---|---|
%c | 98 | b |
%d | 98.5 | 98 |
%5d | 98.5 | 98 |
%e | 98.5 | 9.850000e+01 |
%f | 98.5 | 98.500000 |
%7.2f | 98.5 | 98.50 |
%g | 98.5 | 98.5 |
%.6g | 98.5 | 98.5 |
%o | 98 | 142 |
%06o | 98 | 000142 |
%x | 98 | 62 |
%X | 98 | 62 |
|%s| | "Feburary" | |Feburary| |
|%10s| | Feburary | | Feburary| |
|%-10s| | Feburary | |Feburary | |
|%.3s| | Feburary | |Feb| |
|%10.3s| | Feburary | | Feb| |
|%-10.3s| | Feburary | |Feb | |
%% | Feburary | % |
%c ASCII字符
%d 十进制数
%e 浮点数([-]d.precisione[+-]dd)
%f 浮点数([-]ddd.precision)
%g %e或%f的变形,去掉结尾的零
%o 无符号八进制数
%s 字符串
%x 无符号十六进制数,a-f表示9-15
%X 无符号十六进制数,A-F表示9-15
%% 百分号
% 与控制字符之间可以出现额外的参数:
- 表达式在它所处的域中左对齐
width 当需要时, 把域的宽度填充到该值, 前导的 0 表示用 0 填充
.prec 字符串最大宽度, 或小数点后保留的位数
转义字符:
\a 警告
\b 退格
\f 换页符
\n 换行符
\r 回车
\t 水平制表
\v 垂直制表
\ddd 1~3位的8进制数
\xhex 16进制数
\c 任意字符
# awk '{print $2, "comes from", $4}' awk_demo
Adam comes from Boston
Bob comes from Charlotte
Chris comes from Dallas
David comes from Houston
Ellis comes from Illinois
10. 内建函数
gsub(r,s) | 将$0中所有r替换为s,返回替换次数 |
gsub(r,s,t) | 将t中所有r替换为s,返回替换次数 |
index(s,t) | 返回t在s中第一次出现的位置,没出现返回0 |
length(s) | 返回s包含的字符个数 |
match(s,r) | 测试s是否包含r匹配的子串,返回子串的起始位置或0;设置RSTART和RLENGTH |
split(s,a) | 用FS将s分割到数组a中,返回字段的个数 |
split(s,a,fs) | 用fs将s分割到数组a中,返回字段的个数 |
sprintf(fmt,expr-list) | 根据格式字符串fmt返回格式化后的expr-list |
sub(r,s) | 将$0的最左最长的、能被r匹配的子字符串替换为s,返回替换次数 |
sub(r,s,t) | 将t的最左最长的、能被r匹配的子字符串替换为s,返回替换次数 |
substr(s,p) | 返回s中从位置p开始的后缀 |
substr(s,p,n) | 返回s中从位置p开始,长度为n的子字符串 |
toupper(s) | 变大写,返回变化后的字符串 |
tolower(s) | 变小写,返回变化后的字符串 |
system() | 使用 shell 命令 |
# awk '{gsub(/s/,"t");print}' awk_demo
1 Adam 18 Botton
2 Bob 17 Charlotte
3 Chrit 17 Dallat
4 David 19 Houtton
5 Ellit 20 Illinoit
# awk '{print index($0,"s")}' awk_demo
13
0
7
15
7
# awk '{print length($0)}' awk_demo
16
18
17
18
19
# awk '{print match($0, "s"),RSTART,RLENGTH}' awk_demo
13 13 1
0 0 -1
7 7 1
15 15 1
7 7 1
# awk '{print split($0,a),a[1],a[2],a[3],a[4]}' awk_demo
4 1 Adam 18 Boston
4 2 Bob 17 Charlotte
4 3 Chris 17 Dallas
4 4 David 19 Houston
4 5 Ellis 20 Illinois
# awk '{print sprintf("%-10s",$2)}' awk_demo
Adam
Bob
Chris
David
Ellis
# awk '{print sub(/s/, "t");print}' awk_demo
1
1 Adam 18 Botton
0
2 Bob 17 Charlotte
1
3 Chrit 17 Dallas
1
4 David 19 Houtton
1
5 Ellit 20 Illinois
# awk '{print substr($0,12)}' awk_demo
oston
arlotte
Dallas
Houston
Illinois
# awk '{print substr($0,12,5)}' awk_demo
oston
arlot
Dalla
Houst
Illin
# awk '{print toupper($4)}' awk_demo
BOSTON
CHARLOTTE
DALLAS
HOUSTON
ILLINOIS
# awk '{print tolower($4)}' awk_demo
boston
charlotte
dallas
houston
illinois
# awk 'BEGIN{system("echo 111")}'
111
# awk 'BEGIN{system("date")}'
Wed Mar 16 10:59:33 CST 2022
11. 流程控制语句
(1)条件语句:三种格式
if( expression )
action1
[else
action2]
if ( expression ) {
statement1
statement2
}
expression ? action1 : action2
# cat awk_if.awk
{
if ($1 <= 3)
$1 = 5 - $1
else
$1 = 10 - $1
print
}
# awk -f awk_if.awk awk_demo
4 Adam 18 Boston
3 Bob 17 Charlotte
2 Chris 17 Dallas
6 David 19 Houston
5 Ellis 20 Illinois
(2)循环语句:三种格式
while (condition)
action
do
action
while (condition)
for ( set_counter ; test_counter ; increment_counter )
action
# awk 'BEGIN{ i=10;while(i>=5) {print i;i--} }'
10
9
8
7
6
5
break:跳出循环体
# awk 'BEGIN{ i=10;while(i>=5) {if (i==7) {break} print i;i--} }'
10
9
8
continue:跳出当前循环
# awk 'BEGIN{ i=10;while(i>=5) {i--;if (i==7) {continue} print i} }'
9
8
6
5
4
(3)特殊流程控制:
getline:立刻读取下一行数据(复制给$0,重新设置NF、NR和FNR)
# awk '/A/ {getline;print}{print}' awk_demo
2 Bob 17 Charlotte
2 Bob 17 Charlotte
3 Chris 17 Dallas
4 David 19 Houston
5 Ellis 20 Illinois
next:继续处理下一行内容
# awk '/A/ {next;print}{print}' awk_demo
2 Bob 17 Charlotte
3 Chris 17 Dallas
4 David 19 Houston
5 Ellis 20 Illinois
exit:如果有END,就跳到END,没有就退出脚本。exit后可以跟一个数字,作为退出的值,默认是0,也可以跟一个表达式,表达式的值作为退出的值
# awk '/D/ {exit} {print} END{print 111}' awk_demo
1 Adam 18 Boston
2 Bob 17 Charlotte
111
12. 数组
# 数组定义
array[subscript] = value
# 数组遍历
for ( variable in array )
action
# 判断下标是否存在
subscript in array
# 利用内建函数split生成数组
split(): n = split(string, array, separator)
#删除数组元素
delete array[subscript]
编辑一个实例脚本:
# cat awk_array.awk
BEGIN{
arrayA["a"]="A"
arrayA["b"]="B"
arrayA["c"]="C"
for (key in arrayA)
print key,arrayA[key]
print "c" in arrayA
delete arrayA
print arrayA["c"]
for (i=1;i<=5;i++)
for (j=1;j<=5;j++)
arrayB[i,j]=0
for (key in arrayB)
print key,":",arrayB[key]
}
{
split($0, arrayC, " ")
key=1
while (key <= NF)
{
print arrayC[key]
key++
}
}
这个脚本中,在BEGIN中首先定义了一个关联数组,然后遍历它,删除它。之后定义了一个模拟的二维数组,遍历它。最后使用 split 函数将 awk_demo 中的数据拆分成数组,也遍历。
# awk -f awk_array.awk awk_demo
a A
b B
c C
1
42 : 0
14 : 0
43 : 0
15 : 0
44 : 0
21 : 0
45 : 0
22 : 0
51 : 0
23 : 0
52 : 0
24 : 0
53 : 0
25 : 0
54 : 0
31 : 0
55 : 0
32 : 0
33 : 0
34 : 0
11 : 0
35 : 0
12 : 0
41 : 0
13 : 0
1
Adam
18
Boston
2
Bob
17
Charlotte
3
Chris
17
Dallas
4
David
19
Houston
5
Ellis
20
Illinois
13. 自定义函数
statements可以包含一个return语句
function name (parameter-list)
{ statements }
一个带有参数$1的函数被调用时($1是一个普通变量),函数接收到的参数是变量值的一份拷贝,不是变量本身。 当数组作为函数的参数时,是引用,可以改变原数组的元素。 在函数体内部,参数是局部变量(只在函数执行时存在),它们与程序中其他同名变量没有关联。 如果函数体内某个变量没有出现在参数列表中,它就是全局变量。 如果想设置私有的局部变量,将该变量包含在参数列表的末尾。
# cat awk_func.awk
function max(x,y) {
return x > y ? x : y
}
BEGIN{
print max(1, max(2, 3))
}
# awk -f awk_func.awk
3