awk指令
1. awk 简介
awk是linux中的一个强大的文本分析工具,它为文本分析提供了一个类编程环境,允许我们修改和重新组织文件。 awk 有3个不同的版本,分别是awk、nawk和gawk,未作特别说明,一般指gawk,gawk 是 AWK 的 GNU 版本。
awk 程序让流编辑迈上了一个新的台阶,它提供了一种编程语言,而不仅仅是一个编辑器命令。在awk编程语言中,我们可以做下面的事情:
- 定义变量来保存数据
- 使用算术和字符串操作符来处理数据
- 使用结构化编程概念,比如 if-then 语句和循环,来为数据增加逻辑
- 提取数据文件中的数据元素,并将它们按照另一顺序或格式重新放置,从而生成格式化报告
2. awk 的命令格式
awk 程序的基本格式如下
awk options '/search pattern1/ {Actions}
/search pattern2/ {Actions}' file
其中, options 的可用选项如下:
选项 | 描述 |
---|---|
-F fs | 指定行中分割数据字段的字段分隔符 |
-f file | 指定读取程序的文件名 |
-v var=value | 定义 awk 程序中的一个变量及其默认值 |
-mf N | 指定要处理的数据文件中的最大字段数 |
-mr N | 指定数据文件中的最大数据行数 |
-W keyword | 指定 awk 的兼容模式或者警告等级 |
3. awk 的工作方式
- awk 每次读取输入文件中的一行
- 对于读取到的输入文件的每一行,awk 通过给定的模式去匹配该行内容,如果匹配成功,awk 便去执行相应的动作。
- 如果匹配失败,awk 不会去执行任何的相应的动作。
- 在上述章节1所提到的语法中,匹配模式和相应的动作都是可选的,不需要都有
- 如果匹配模式没有给定, awk 会对输入文件的每一行进行相应的指令操作
- 如果相应的指令操作没有给定, awk 会打印出用匹配模式匹配到的输入行。其中打印操作是默认的 awk 操作。
- 任何指令的空括号都不做任何动作。它不会执行默认的打印操作。
- 操作中的每个语句都要用分号分隔。
4. awk 的基本使用方法
4.1 从命令行读取程序脚本
awk 程序脚本用一对花括号来定义,必须将脚本命令放到两个花括号中。由于 awk 命令假定脚本是单个文本字符串,所以必须将脚本放到单引号中。举个栗子:
awk '{print $0}'
这个程序脚本定义了一个命令,即 print 命令。该程序会一直等待从 STDIN 输入的文本,然后打印出来。在命令行运行脚本的效果如下:
sk@sk ~ $ awk '{print $0}'
hello world
hello world
hello sk
hello sk
4.2 使用数据字段变量
awk 会自动给每行中的每一个字段分配一个变量。每一个字段通过字段分割符来划分,默认的字段分隔符是任意的空白字符(空格或者制表符)。默认情况下,awk 会将如下变量分配给每一个字段。
变量 | 变量含义 |
---|---|
$0 | 整行文本 |
$1 | 文本行中的第1个字段 |
$2 | 文本行中的第2个字段 |
… | … |
$n | 文本行中的第n个字段 |
假设我们有如下的文件
$cat employee.txt
100 Thomas Manager Sales $5,000
200 Jason Developer Technology $5,500
300 Sanjay Sysadmin Technology $7,000
400 Nisha Manager Marketing $9,500
500 Randy DBA Technology $6,000
然后,我们只打印每个人的人名。
awk '{print $2}' employee.txt
输出结果如下:
Thomas
Jason
Sanjay
Nisha
Randy
4.3 在程序脚本中使用多个命令
在 awk 的程序脚本中,采用分号(;)来区分每条命令。比如:
echo "hello world" | awk '{$2=$2"!"; $3="my name is sk"; print $0}'
上述指令,是将 2,也就是"world",重新赋值成"world!"。然后,又添加了 3 变量,最后打印整行文本。运行结果如下:
hello world! my name is sk
4.4 在处理数据前运行脚本
有时候,我们需要 awk 在处理每行文本前运行某些指令,比如打印报告开头部分。此时,我们可以使用关键字 BEGIN 来标示 awk 开始部分。 awk 会在处理每行文本前执行 BEGIN 关键字后面指定的程序脚本。
比如,我们给 employee.txt 文本添加一个报告开头,然后,在打印出来。
awk '
BEGIN {print "工号 姓名 岗位 职能 薪水"; print "---- ---- ---- ---- ----"}
{print $0}
' employee.txt
运行结果如下
工号 姓名 岗位 职能 薪水
---- ---- ---- ---- ----
100 Thomas Manager Sales $5,000
200 Jason Developer Technology $5,500
300 Sanjay Sysadmin Technology $7,000
400 Nisha Manager Marketing $9,500
500 Randy DBA Technology $6,000
4.5 在处理数据后运行脚本
跟 BEGIN 类似,END 关键字允许我们指定一个程序脚本,然后 awk 在读完所有数据之后,执行 END 指定的程序脚本。采用这种方法,我们可以个报告添加结尾。比如,我们给 employee.txt 文本添加一个报告结尾。
awk '
BEGIN {print "工号 姓名 岗位 职能 薪水"; print "---- ---- ---- ---- ----"}
{print $0}
END {print "----------------------"; print "This is all employee info"}
' employee.txt
运行结果如下:
工号 姓名 岗位 职能 薪水
---- ---- ---- ---- ----
100 Thomas Manager Sales $5,000
200 Jason Developer Technology $5,500
300 Sanjay Sysadmin Technology $7,000
400 Nisha Manager Marketing $9,500
500 Randy DBA Technology $6,000
----------------------
This is all employee info
4.6 从文件中读取程序
有时候, awk 指令中,需要执行的脚本程序太多,我们就可以将程序存储到文件中,然后再在命令行中引用。 比如,我们有如下的脚本文件:
$ cat cropt_employee
BEGIN {print "工号 姓名 岗位 职能 薪水"; print "---- ---- ---- ---- ----"}
{print $0}
END {print "----------------------"; print "This is all employee info"}
然后,我们在命令行中用 awk 指令去调用这个脚本文件。调用方法采用章节2中的 -f file options 选项。调用方法如下:
awk -f scropt_employee employee.txt
执行的结果跟上面 3.5 小节的一样。
5. awk 的高级使用方法
5.1 使用变量
awk 有两种变量, 分别是内建变量和自定义变量。
5.1.1 内建变量
awk 的内建变量有很多,其中主要的有:
变量 | 描述 |
---|---|
FS | 输入字段分隔符 |
RS | 输入数据行分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出数据行分割符 |
FS 变量会将输入数据按照 FS 变量来分隔字段。比如
echo -e "I,love,hinata" | awk 'BEGIN {FS=","}{print $1, $2, $3}'
然后输出是
I love hinata
而 RS 与 FS 不同的是, 它是根据数据行来作为分隔符的。比如,你有一些用户数据,包括用户名,用户住址,用户电话号码,但是每一个字段是按照一行来存储的,然后每一个用户之间的信息用一个空白行来分割,就像这样:
$ cat user_info.txt
sk
china
0512-215215
hinata
china
0512-13141314
这时,你就可以用 RS=”” 来区分每一个用户,用 FS=’\n’来区分每一个用户中的字段。如下所示:
awk 'BEGIN {RS=""; FS="\n"; print "姓名 地址 电话"} {print $1, $2, $3}' user_info.txt
输入如下
姓名 地址 电话
sk china 0512-215215
hinata china 0512-13141314
OFS 和 ORS 与 FS 和 RS 相反,它们是控制输出数据的分隔。比如:
echo -e "i love hinata" | awk 'BEGIN {OFS="-->"}{print $1, $2, $3}'
输出结果如下
i-->love-->hinata
5.1.2 自定义变量
awk 允许我们定义自己的变量在程序脚本中使用。比如,我们创建一个简单的加法, 定义一个 sum 变量,计算两个数的和。
echo "1+2" | awk 'BEGIN {FS="+"; sum=0}{sum=sum+$1+$2} END {print sum}'
最后的输出结果是 3
5.2 处理数组
linux 中的数组是 关联数组。 关联数组中的索引值可以是任何的文本字符串,但是每个索引字符串都必须是唯一的。这跟其他语言(如 python)中的字典是同一个概念。
5.2.1 定义数组
数组的定义如下
var[index] = element
比如,
awk '
BEGIN {
var[1]=34
var[2]=3
total=var[1]+var[2]
print total
}
'
5.2.2 遍历数组
可以使用 for 语句来遍历数组
for (var in array) {
statements
}
awk '
BEGIN {
var["i"] = 1
var["k"] = 2
var["s"] =9
for ( test in var ) {
print "Index:", test, " - Value", var[test]
}
}
'
5.3 使用模式
awk 使用模式来过滤数据行。 BEGIN 和 END 关键字是用来在读取数据流之前或者之后执行命令的特殊模式。我们也可以使用其他的模式来在数据流中出现匹配数据时执行一些命令。
5.3.1 正则表达式
使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的花括号前面。awk 会用正则表达式对数据行中的所有数据字段进行匹配,包括字段分隔符。若匹配成功,则执行相应的指令。
echo -e "hello world \nhello hinata" | awk 'BEGIN {print "start"} /hinata/{print $0}'
5.3.2 匹配操作符
匹配操作符允许将正则表达式限定在数据行中的特定数据字段。匹配操作符是波浪线(~)。
下面表示的是用 data2 去匹配第二个字段。
echo -e "data11,data12,data13\ndata21,data22,data23" | awk -F, '$2 ~ /data2/{print $0}'
也可以用 ! 符号来排除正则表达式的匹配。
echo -e "data11,data12,data13\ndata21,data22,data23" | awk -F, '$2 !~ /data2/{print $0}'
5.3.3 数学表达式
awk 允许我们在匹配模式中使用数学表达式。如下所示,是显示所有属于 root 用户组的系统用户。
awk -F: '$4==0{print $1}' /etc/passwd
也可以对文本数据使用表达式,但是数据和模式必须完全匹配。
echo -e "data11,data12,data13\ndata21,data22,data23" | awk -F, '$1=="data1"{print $0}'
echo -e "data11,data12,data13\ndata21,data22,data23" | awk -F, '$1=="data11"{print $0}'
5.4 结构化命令
5.4.1 if 语句
awk 编程语言支持标准的 if-then-else 格式的 if 语句。格式如下:
if (condition)
statement1
如:
echo -e "2\n4\n6\n8" | awk '{if ($1 > 4) print $1 }'
echo -e "2\n4\n6\n8" | awk '{if ($1 > 4){x=$1 * 2; print x}}'
5.4.2 while 语句
while 语句的格式如下
while (condition) {
statements
}
如有如下的数据文件:
$ cat data5
110,120,145
223,345,666
234,563,854
我们计算每一行的平均值为:
awk '
BEGIN {
FS = ","
num = 4
}
{
total = 0
i = 1
while (i < num){
total += $i
i++
}
avg = total / 3
print "Average:", avg
}
' data5
输出如下:
Average: 125
Average: 411.333
Average: 550.333
5.4.3 do-while 语句
do-while 语句和 while 语句类似,只不过, do-while 语句至少执行一次。格式如下:
do {
statement
} while (condition)
5.4.4 for 语句
awk 支持 c 风格的 for 循环, 格式如下:
for(variable assignment; condition; iteration process) {
statement
}
比如,我们将上面的求平均值的例子用 for 写出来为:
awk '
BEGIN {
FS = ","
num = 4
}
{
total = 0
for (i=1; i<num; i++){
total += $i
}
avg = total / 3
print "Average:", avg
}
' data5
5.5 格式打印
awk 支持 c 语言中的 printf , 允许指定具体的如何显示数据的指令。 printf 的格式如下:
printf "format string", var1, var2
“format string” 会用文本元素和格式化指定符来具体指定如何呈现格式化输出。格式化指定符是一种特殊的代码,它会指明什么类型的变量可以显示以及如何显示。指定符的格式为:
%[modifier]control-letter
其中 modifier 是修饰符, 分别为
修饰符 | 描述 |
---|---|
width | 指定输出字段最小宽度的数字值 |
prec | 指定浮点数中小数点后面位数的数字值,或者文本字符串中显示的最大字符数 |
- | 指明在向格式化空间中放入数据时采用左对齐而不是右对齐 |
control-letter 是指明显示什么类型数据值的单字符码,各个类型的字符码表示如下
控制字母 | 描述 |
---|---|
d | 显示一个整数值 |
i | 同d |
e | 用科学计数法显示一个数 |
f | 显示一个浮点值 |
o | 显示一个八进制数 |
s | 显示一个文本字符串 |
x | 显示一个十六进制 |
X | 显示一个十六进制,但是用大写字母A~F |
比如我们用科学计数法显示一个数
awk 'BEGIN {
x = 12345 * 6789
printf "the anwser is: %e\n", x
}'
格式化输出字符串
awk 'BEGIN {
FS="\n"
RS=""
printf "%-10s %-10s %s\n", "name", "address", "mobile"
printf "%-10s %-10s %s\n", "----", "----", "----"
}
{
printf "%-10s %-10s %s\n", $1, $2, $3
}
' user_info.txt
输出结果如下
name address mobile
---- ---- ----
sk china 0512-215215
hinata china 0512-13141314
格式化打印浮点数
awk '
BEGIN {
FS = ","
num = 4
}
{
total = 0
for (i=1; i<num; i++){
total += $i
}
avg = total / 3
printf "Average: %-5.1f\n", avg
}' data5
Average: 125.0
Average: 411.3
Average: 550.3
5.6 使用函数
5.6.1 数学函数
awk 提供了内建的数学函数,比如 atan2(x,y), cos(x), int(x)等等。
5.6.2 字符串函数
内建的字符串函数有 length([s]), split(s, a[,t]), gsub(r,s,[,t])等等。
5.6.3 时间函数
awk 提供一些函数来帮助处理时间值,比如:
awk 'BEGIN {
date = systime()
day = strftime("%Y-%m-%d %H:%M:%S", date)
print day
}
'
5.6.4 自定义函数
awk 允许我们创建自己的自定义函数,注意自定义函数必须放置在所有代码块之前,有助于将函数代码和 awk 程序的其他部分分开。
awk '
function myprint() {
printf "%-16s - %s\n", $1, $3
}
BEGIN {
FS="\n"
RS=""
}
{
myprint()
}
' user_info.txt
运行结果如下:
sk - 0512-215215
hinata - 0512-13141314
同时,我们也可以创建自己的函数库,将函数写到一个文件里面,然后一同调用。我们可以使用多个 -f 参数来使用多个 awk 脚本文件和函数库文件。
$ cat funclib
function myprint() {
printf "%-16s - %s\n", $1, $3
}
function myrand(limit) {
return int(limit * rand())
}
function printthird() {
print $3
}
$ cat script
BEGIN {
rand_num = myrand(10)
print rand_num
}
awk -f funclib -f script