文本处理三剑客之awk语句用法
什么是awk?
awk
是一种用于文本处理和数据转换的强大工具,它是一种文本处理语言,通常用于处理结构化的文本数据,如日志文件、CSV文件等。awk
提供了灵活的文本匹配和处理能力,使您能够从文本数据中提取、转换和汇总信息。
基本语法
awk
的基本语法结构如下:
awk 'pattern { action }' input_file
awk 选项 ‘BEGIN{开始处理之前};{处理中};END{处理结束后}’ 文件名
注:awk处理内容里面必须是双引号,不能是单引号
pattern
:用于匹配输入文本的模式。如果省略模式部分,awk
将对所有输入行执行相应的动作。{ action }
:当模式匹配时执行的动作。可以是一条或多条命令,用花括号括起来。input_file
:要处理的输入文件。如果省略此部分,awk
将从标准输入读取数据。
内置变量
内置变量 | 功能 |
---|---|
NF | 当前处理的行的字段个数 |
NR | 当前处理的行的行号 |
FNR | 读取文件的记录数(行号),从1开始,新的文件重新从1开始计数 |
$0 | 当前处理的行的整行内容(就是:表示一行的内容) |
$n | 当前处理行的第n个字段(就是:第n列) |
FILENAME | 被处理的文件名 |
FS | 指定每行的字段分隔符,默认为空格或制表符(相当于选项-F) |
OFS | 输出字段的分隔符,默认也是空格 |
RS | 行分割符。awk从文件上读取资料时,将根据RS的定义把资料切割成多条记录,而awk一次仅读取一条记录,预设值为“\n” |
ORS | 输出分割符,默认也是换行符 |
赋值操作符
操作符 | 定义 |
---|---|
++ | 变量加1 |
– | 变量减1 |
+= | 将加的结果赋给变量 |
-= | 将减的结果赋给变量 |
*= | 将乘的结果赋给变量 |
/= | 将除的结果赋给变量 |
%= | 将取模的结果赋给变量 |
^= | 将取幂的结果赋给变量 |
**= | 将取幂的结果赋给变量(和^=相同) |
awk命令中,打印的结果可以基于文件中的内容,也可以不基于文件内容,awk
只是需要一个参数,如下列命令所示:
此时我的echo命令没有输出任何内容,但是我仍然打印出来了一行内容,这是因为我的print也是打印的命令,而print后面就可以输入我先输出的内容,这也就是说我'{print "hello world"}'打印的动作和前面的echo没有任何关系。
[root@centos2 ~]# echo | awk '{print "hello world"}'
hello world
由于上述echo命令没有任何内容,所以在awk命令执行时,只读取了一行,用如下命令我们可以输出多行内容,echo输出了两个\n换行符,也就是三行,所以我们打印的时候也是读取三行。
[root@centos2 ~]# echo -e "\n\n" | awk '{print "hello world"}'
hello world
hello world
hello world
awk
命令打印文件里面所有的内容
直接使用print,后面不跟任何参数
[root@centos2 ~]# awk '{print}' numbers
10
20
30
40
50
使用awk
命令测试匹配文件中是否存在空行
//我们查看test这个文件这两个存在有两行空行
[root@centos2 ~]# cat test
hello world
name = root
sed awk grep
192.168.195.100
255.255.255.0
.Ah "Major Heading"
See Section 1.4
See Section 12.9
(See Section 12.9)
//此命令表示若匹配到空行则输出This is a blank line,表示这行为空行
[root@centos2 ~]# awk '/^$/{print "This is a blank line"}' test
This is a blank line
This is a blank line
由此可得,test文件中有两行空白行
//相对于上面的命令来说,空行的数量需要我们去数出现了几行This is a blank line,从而得出有几行空行,而下列命令则可以直接计算出有多少行空行
[root@centos2 ~]# awk '/^$/{x++}END{print x}' test
2
x++ 在返回结果前递增x的值(前缀)
++x 在返回结果后递减x的值(后缀)
举一反三,我们还可以用这种方式测试文件中内容是否为整数或字符串
[root@centos2 ~]# awk '/[0-9]+/{print "That is an integer"}' test
[root@centos2 ~]# awk '/[a-zA-Z]+/{print "This is a string"}' test
awk
命令指定匹配文件中以什么符号作为分隔符去提取内容,“-F“选项
[root@centos2 ~]# cat tab
hello world
name = root
sed awk grep
192. 168.195. 100 //此行两处为tab键
255.255.255.0
//默认情况下,使用awk去匹配文件中内容的时候是以文本中空格符为分隔符
[root@centos2 ~]# awk '{print $1":"$2}' tab
hello:world
name:=
sed:awk
192.:168.195.
255.255.255.0:
//我们可以是用-F选项去指定分隔符,“\t”代表的是tab键,这里是指定以tab键为分隔符提取文件中第一列内容
[root@centos2 ~]# awk -F"\t" '{print $1}' tab
hello world
name = root
sed awk grep
192.
255.255.255.0
awk
命令“-F”指定多分隔符
语法:awk -F'[a:b]+' '{print 处理的内容}' filename
[a:b]+中的a,b代表的是分隔符,+则是表示多个分隔符中至少出现一个
案例:
取出文件中的ip地址,不包括子网掩码
//文件test内容:
[root@centos2 ~]# cat test
hello world
name = root
sed awk grep
IP: 192.168.195.100/24
255.255.255.0
.Ah "Major Heading"
See Section 1.4
See Section 12.9
(See Section 12.9)
//此命令中,把空格符和/都当做是分隔符,就会把/24的/当做分隔符,而后面的24自然就不会打印出来
[root@centos2 ~]# cat test | grep 'IP: ' | awk -F'[ /]+' '{print $2}'
192.168.195.100
[ /]中为一个空格和一个/符
awk
命令匹配出来的结果默认是以空格为分隔符,然而可能某些情况下我们需要指定他的分隔符,命令如下:
此文件中以空格为分隔符,显示三个参数
[root@centos2 ~]# cat a
john Robinson 666-555-1111
//默认情况下$1,$2,$3中的逗号就是代表是以空格分开
[root@centos2 ~]# awk '{print $1,$2,$3}' a
john Robinson 666-555-1111
//我们可以将其输出结果换一种分隔符进行显示,如$1":"$2":"$3,使用冒号隔开
[root@centos2 ~]# awk '{print $1":"$2":"$3}' a
john:Robinson:666-555-1111
//我们还可以将匹配到的行里面的内容进行调换
[root@centos2 ~]# awk '{print $3,$2,$1}' a
666-555-1111 Robinson john
在awk
语句中加入变量
[root@centos2 ~]# awk 'BEGIN{a="hello world"}{print a}' a
hello world
[root@centos2 ~]# echo | awk 'BEGIN{x=1;y=x+1}{print y}'
2
//先在开始处理前使用BEGIN定义变量,从而的出主体部分的运算的值,$(one+two)则是$3,表示第三个输出内容,从echo输出的内容可知,第三个输出的是c,所以我们的结果就是c。
[root@centos2 ~]# echo a b c d | awk 'BEGIN{one=1;two=2}{print $(one+two)}'
c
使用awk
命令,实现计算器的效果
[root@centos2 ~]# awk 'BEGIN{print 1+2}'
3
[root@centos2 ~]# awk 'BEGIN{print 4/10}'
0.4
[root@centos2 ~]# awk 'BEGIN{print 4**2}'
16
[root@centos2 ~]# awk 'BEGIN{print 4^2}'
16
[root@centos2 ~]# awk 'BEGIN{print 10%2}'
0
实例说明:
在学生的姓名后面有5个成绩,请计算出每个学生的平均成绩:
[root@centos2 ~]# cat b
iohn 85 92 78 94 88
andrea 89 92 75 90 86
jasper 84 88 80 92 84
[root@centos2 ~]# awk '{total=$2+$3+$4+$5+$6;avg=total/5;print $1,avg}' b
iohn 87.4
andrea 86.4
jasper 85.6
//第一步就是将所有分数相加,除了第一行是姓名,后面五行都是分数,也就$2+$3+$4+$5+$6用total表示,avg为平均数,用total/5得出,最后打印姓名以及每个人的平均成绩
系统变量
awk 中有许多系统变量或内置变量。awk有两种类型的系统变量。第一种类型定义的变量默认值可以改变,例如默认的字段和记录分隔符。第二种类型定义的变量的值可用于报告或数据处理中。例如当前记录中字段的数量,当前记录的数量等。这些可以由awk自动更新,例如,当前记录的编号和输入文件名。
有一组默认值会影响对记录和字段的输入和输出的识别。系统变量FS定义字段分隔符。它的默认值为一个空格,这将提示awk可以用若干个空格和/或制表符来分隔字段。FS可以被设置为任何单独的字符或一个正则表达式,前面,我们将分隔符改变为逗号,为的是读取一个名字和地址的列表。
和FS等效的输出是OFS,它的默认值为一个空格。我们将看到一个例子来简单地重新定义OFS。
awk将变量NF定义为当前输入记录的字段个数。改变NF的值会有副作用。当$0(字段)和NF被改变时将产生令人费解的相互作用,尤其是当NF 减小时。增加NF值会创建新的(空的)字段,并重新建立$0,字段由OFS的值来分隔。
关系操作符和布尔操作符
关系操作符
操作符 | 描述 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
== | 相等的 |
!= | 不等的 |
~ | 匹配 |
!- | 不匹配 |
关系表达式可用在模式中来控制特殊的操作。例如,若果我们想要限定要处理的记录只包含5个字段,则可以使用下面的表达式:
NF==5
这个关系表达式将NF(每个输入记录的字段数)的值和5进行对比,如果结果为真,那么就进行相应的操作,反之,则不进行操作。
注:关系操作符“(相等)”和赋值操作符“=(等于)”是不同的。不能用“=”代替“”来检验相等性。
布尔操作符
操作符 | 定义 |
---|---|
|| | 逻辑或 |
&& | 逻辑与 |
! | 逻辑非 |
给定两个或多个表达式,只有当给定的表达式之一为真(非零或非空)时,使用操作符||的表达式才为真。只有当&&操作符连接的两个表达式的值都为真时结果才为真。
比如:
NF == 4 && NR > 2
表示字段的数量必须等于4并且行号必须大于2,才执行相应指令
注:&&的优先级高于||
FS和OFS的用法
FS指定输入时的分隔符,与-F实现相同的效果
[root@centos2 ~]# cat tab
hello world
name = root
sed awk grep
192. 168.195. 100 //此行两处为tab键
255.255.255.0
[root@centos2 ~]# awk 'BEGIN{FS="\t"}{print $1}' tab
hello world
name = root
sed awk grep
192.
255.255.255.0
OFS指定输出时的分隔符
//同样以tab这个文件为例
[root@centos2 ~]# cat tab
hello world
name = root
sed awk grep
192. 168.195. 100 //此行两处为tab键
255.255.255.0
//使用OFS时需指定主题中输出的分隔符为“,”,也就是$1,$2这种写法,不然OFS无法覆盖默认分隔符
[root@centos2 ~]# awk 'BEGIN{FS=" ";OFS="|";}{print $1,$2}' tab
hello|world
name|=
sed|awk
192.|168.195.
255.255.255.0|
NR的用法
使用NR显示文件内容行号
[root@centos2 ~]# awk '{total=$2+$3+$4+$5+$6;avg=total/5;print NR "." $1,avg}' b
1.iohn 87.4
2.andrea 86.4
3.jasper 85.6
使用NR去匹配到我们所要匹配的行号,NR=="n"
[root@centos2 ~]# cat b
iohn 85 92 78 94 88
andrea 89 92 75 90 86
jasper 84 88 80 92 84
//使用NR可以指定打印的行号
[root@centos2 ~]# awk 'NR==2{print}' b
andrea 89 92 75 90 86
NF的用法
使用NF显示文本行中有多少列
[root@centos2 ~]# cat b
iohn 85 92 78 94 88
andrea 89 92 75 90 86
jasper 84 88 80 92 84
[root@centos2 ~]# awk '{print NF}' b //显示每行有多少列
6
6
6
N F 在文本处理中是最后一列的意思,通常使用 NF在文本处理中是最后一列的意思,通常使用 NF在文本处理中是最后一列的意思,通常使用NF来打印文本行的最后一列数据
[root@centos2 ~]# cat b
iohn 85 92 78 94 88
andrea 89 92 75 90 86
jasper 84 88 80 92 84
[root@centos2 ~]# awk '{print $NF}' b
88
86
84
由于NF代表的是最后一列的意思,那在我们想要得知倒数第二行,然而又因为存在太多列数不清的情况下,该怎样去匹配到倒数第二列,命令如下:
使用$(NF-n)
表示倒数第二行,依次类推,可以得到倒数第几行的内容
[root@centos2 ~]# awk '{print $(NF-1)}' b
94
90
92
我们也可以使用NF去打印只有多少列的行,比如只打印出一行中有三列的行:
[root@centos2 ~]# cat tab
hello world
name = root
sed awk grep
192. 168.195. 100
255.255.255.0
[root@centos2 ~]# awk 'NF==3{print $0}' tab //$0表示行本身,也就是整行
name = root
sed awk grep
192. 168.195. 100
结合NR和NF使用,更加精准的匹配出我们想取出的哪一列哪一行:
[root@centos2 ~]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 382M 0 382M 0% /dev
tmpfs 399M 0 399M 0% /dev/shm
tmpfs 399M 11M 388M 3% /run
tmpfs 399M 0 399M 0% /sys/fs/cgroup
/dev/mapper/cl-root 17G 6.6G 11G 39% /
/dev/sda1 976M 136M 774M 15% /boot
tmpfs 80M 0 80M 0% /run/user/0
[root@centos2 ~]# df -h | awk 'NR=1{print $(NF-1),$NF}'
Mounted on
0% /dev
0% /dev/shm
3% /run
0% /sys/fs/cgroup
39% /
15% /boot
0% /run/user/0
[root@centos2 ~]# df -h | awk 'NR==1{print $(NF-1),$NF}'
Mounted on
[root@centos2 ~]# df -h | awk 'NR==1{print $(NF-1),$NF}NR!=1{print $NF}'
Mounted on
/dev
/dev/shm
/run
/sys/fs/cgroup
/
/boot
/run/user/0
[root@centos2 ~]# df -h | awk '{print $(NF-1),$NF;next}{print $NF}'
Mounted on
0% /dev
0% /dev/shm
3% /run
0% /sys/fs/cgroup
39% /
15% /boot
0% /run/user/0
printf的用法
在Linux中,printf
是一个用于格式化输出的命令。它可用于将数据按照指定的格式打印到标准输出(屏幕)上或保存到文件中。
printf
的基本语法如下:
printf format [arguments]
其中,format
是要输出的格式化字符串,arguments
是要输出的表达式或变量。
格式化字符串由普通的文本和格式说明符(format specifiers)组成,格式说明符以百分号(%)开始,并用于指定输出的格式。常用的格式说明符包括:
%s
:输出字符串%d
:输出有符号十进制数%f
:输出浮点数%c
:输出字符%x
:输出十六进制数
在格式化字符串中,可以使用多个格式说明符,并且可以根据需要插入其他文本。格式说明符可以使用不同的参数来控制输出格式,如指定宽度、精度、对齐方式等。更详细的格式说明符参数可以参考相关的文档或教程。
需要注意的是,printf
并不会自动换行,如果需要换行,可以在格式化字符串的末尾加上\n
。
[root@centos2 ~]# echo | awk '{printf "hello world"}'
hello world[root@centos2 ~]#
在格式化字符串中,可以使用多个格式说明符,并且可以根据需要插入其他文本。格式说明符可以使用不同的参数来控制输出格式,如指定宽度、精度、对齐方式等。
实例:
[root@centos2 ~]# ll
total 44
-rw-r--r--. 1 root root 27 Sep 18 15:06 a
-rw-r--r--. 1 root root 92 Sep 15 09:16 abc
-rw-------. 1 root root 1344 Jul 27 16:22 anaconda-ks.cfg
-rwxr-xr-x. 1 root root 7664 Sep 11 17:35 apache-version.sh
-rw-r--r--. 1 root root 64 Sep 18 20:58 b
-rw-r--r--. 1 root root 106 Sep 15 17:01 c
-rw-r--r--. 1 root root 15 Sep 18 14:10 numbers
-rw-r--r--. 1 root root 69 Sep 15 00:43 tab
-rw-r--r--. 1 root root 114 Sep 19 12:48 test
-rwxr-xr-x. 1 root root 25 Sep 14 09:12 test.sh
//下列命令中用printf产生了一个输出。它输出不同的两个字段上的字符串(%s)和十进制值(%d),对每个格式说明必须提供一个相应的参数,下列命令中%d对应的是$5,%s对应的是$9,相对应的参数也必须要符合与之对应的格式,否则无法匹配。
[root@centos2 ~]# ll | awk 'NR!=1{printf "%d\t%s\n",$5,$9}'
27 a
92 abc
1344 anaconda-ks.cfg
7664 apache-version.sh
64 b
106 c
15 numbers
69 tab
114 test
25 test.sh
printf语句规定输出域的宽度和对齐方式
在printf
命令中,可以使用一些参数来控制输出域的宽度和对齐方式。以下是常用的参数:
-
-(减号):左对齐。通过在格式说明符前加上减号,可以使得输出左对齐,默认情况下,输出是右对齐的。
示例:
“|%ns|” n代表的是输出域耳朵宽度,若为10,则宽度为10个字符长度 [root@centos2 ~]# awk 'BEGIN{printf "|%s|\n","hello"}' |hello| [root@centos2 ~]# awk 'BEGIN{printf "|%-10s|\n","hello"}' |hello |
-
数字(整数):指定输出域的宽度。通过在格式说明符前加上数字,可以指定输出域的最小宽度,如果输出内容长度小于宽度,则会自动在左侧补足空格。
示例:
[root@centos2 ~]# awk 'BEGIN{printf "|%s|\n","hello"}' |hello| [root@centos2 ~]# awk 'BEGIN{printf "|%10s|\n","hello"}' | hello|
-
0(数字零):用零填充。通过在宽度参数前加上零字符,可以使得输出域被零填充而不是默认的空格填充。
示例:
[root@centos2 ~]# awk 'BEGIN{printf "|%d|\n",54}' |54| [root@centos2 ~]# awk 'BEGIN{printf "|%05d|\n",54}' |00054|
实例:计算文件中数字的总和
让我们通过一个简单的示例来说明awk
的基本用法。假设我们有一个包含数字的文本文件numbers.txt
,内容如下:
10
20
30
40
我们希望使用awk
计算这些数字的总和。我们可以这样做:
[root@centos2 ~]# awk '{sum += $1}END{print sum}' numbers
150
此命令中,{sum += $1}是一个主体,sum是一个变量,而$1则是文本内容中每行的第一列,也就是上述我们数字(10,20,30,40),sum每次匹配都加上文本内容中每行的第一列数字,从而得出上述数字的总和。
{print sum}则是将sum变量打印出来,在awk中不用加上$符号,直接使用变量名即可。
这个awk
命令的解释如下:
{ sum += $1 }
:对每一行执行的动作,将第一个字段($1)的值加到sum
变量中。END { print sum }
:在处理完所有行后执行的动作,打印sum
的值。
运行上述命令,输出结果为:
150
awk
通过逐行读取文件,将每一行分割成字段(默认使用空格分割),然后根据您指定的动作对字段进行操作。
使用场景
awk
非常适合处理结构化的文本数据,如日志文件、CSV文件、配置文件等。以下是一些常见的使用场景:
- 数据提取和过滤:从大型文本文件中提取特定字段或行,例如提取日志中的错误信息。
- 数据转换:将文本数据从一种格式转换为另一种格式,例如将CSV文件转换为JSON格式。
- 数据统计:计算文本文件中的各种统计信息,如总和、平均值、最大值等。
- 文本报告生成:根据文本数据生成报告或摘要。
- 文本格式化:格式化文本输出,以便易于阅读或与其他工具集成。
- 文本搜索和替换:类似于
sed
,awk
也可以用于搜索和替换文本。
实例:数据统计,使用awk
处理统计信息
假设我们有一个文件wenben.txt
,内容如下:
[root@centos2 ~]# cat test
1000
125 Market -125.45
126 Hardware Store -34.95
127 Video Store -7.45
128 Book Store -14.32
129 Gasoline -16.10
我们可以使用awk
来提取特定列的信息。例如,提取我们的开始金额:
//命令中使用\t,也就是tab键,方便后续对齐文本,NR==1,匹配第一行文本
[root@centos2 ~]# awk 'NR==1{print "begin balance:\t" $1}' test
begin balance: 1000
这个命令给定义了balance=$1
这个变量,并使用了next
,意思是在下一条指令中跳过我这一行操作,去做下面的其他行的操作,所以在后面打印的指令中才没有再次打印第一行的文本,若不加上next
,则会再次打印第一行的文本(注1)
[root@centos2 ~]# awk 'NR==1{print "begin balance:\t" $1;balance=$1;next}{print $1,$2,$3}' test
begin balance: 1000
125 Market -125.45
126 Hardware Store
127 Video Store
128 Book Store
129 Gasoline -16.10
注1:
[root@centos2 ~]# awk 'NR==1{print "begin balance:\t" $1;balance=$1}{print $1,$2,$3}' test
begin balance: 1000
1000
125 Market -125.45
126 Hardware Store
127 Video Store
128 Book Store
129 Gasoline -16.10
最后,使用balance+=$NF
表示每匹配一行都用balance去加上最后一列的数字,从而计算出最后剩下的金额数:
[root@centos2 ~]# awk 'NR==1{print "begin balance:\t" $1;balance=$1;next}{print $1,$2,$3;print balance+=$NF}' test
begin balance: 1000
125 Market -125.45
874.55
126 Hardware Store
839.6
127 Video Store
832.15
128 Book Store
817.83
129 Gasoline -16.10
801.73
数据提取和过滤
使用awk提取文件长格式中的文件大小和文件名字,即打印第五个字段($5)和第九个字段($9),并且计算出文件大小的总和,并显示该目录中有多少个文件
[root@centos2 ~]# ll
total 44
-rw-r--r--. 1 root root 27 Sep 18 15:06 a
-rw-r--r--. 1 root root 92 Sep 15 09:16 abc
-rw-------. 1 root root 1344 Jul 27 16:22 anaconda-ks.cfg
-rwxr-xr-x. 1 root root 7664 Sep 11 17:35 apache-version.sh
-rw-r--r--. 1 root root 64 Sep 18 20:58 b
-rw-r--r--. 1 root root 106 Sep 15 17:01 c
-rw-r--r--. 1 root root 15 Sep 18 14:10 numbers
-rw-r--r--. 1 root root 69 Sep 15 00:43 tab
-rw-r--r--. 1 root root 114 Sep 19 12:48 test
-rwxr-xr-x. 1 root root 25 Sep 14 09:12 test.sh
//第一步我们要做的是先将第五个字段和第九个字段打印出来,由于目录的第一行没有我们想要的结果,所以我们在打印的过程中就不匹配第一行内容,使用NR!=1,结果如下:
[root@centos2 ~]# ll | awk 'NR!=1{print $5,$9}'
27 a
92 abc
1344 anaconda-ks.cfg
7664 apache-version.sh
64 b
106 c
15 numbers
69 tab
114 test
25 test.sh
//第二步我们给每一列加上一个解释,第一列代表大小,第二列代表文件名称,并定义两个变量,sum用来将每一行的$5相加,filenum表示每匹配一行则自动加1,从而得到文件数量,在END中进行打印的步骤,由于awk中变量不能写在双引号中,所以我们只需要将固定格式使用双引号引起来,变量则直接写入即可,用\t做分隔符是为了文本对齐,更加清晰。
[root@centos2 ~]# ll | awk 'BEGIN{print "bytes""\t""file"}NR!=1{sum+=$5;filenum++;print $5"\t"$9}END{print "Total:\t" sum "bytes (" filenum " file)"}'
bytes file
27 a
92 abc
1344 anaconda-ks.cfg
7664 apache-version.sh
64 b
106 c
15 numbers
69 tab
114 test
25 test.sh
Total: 9520bytes (10 file)
总之,awk
是一种强大的文本处理工具,适用于各种文本数据处理任务。通过掌握其基本语法和常见用法,您可以更高效地处理和分析文本数据。无论是在命令行中使用还是将其嵌入到脚本中,awk
都是一个强大而灵活的工具,可帮助您处理和分析文本数据。