Shell(1):awk

发布时间 2023-08-08 10:02:55作者: ShineLe

1、概述

awk是一种处理文本文件的语言,是一个强大的文本分析工具。

 

2、awk基本使用

1)用法

awk '条件1{动作1} 条件2{动作2}...' 文件
awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

2)条件

一般使用关系表达式:

  • x > 10 判断变量x是否大于10
  • x == y 判断变量x是否等于变量y
  • A ~ B 判断字符串A中是否包含能匹配B表达式的子字符串
  • !~ B 判断字符串A中是否不包含能匹配B表达式的子字符串

3)动作:①格式化输出printf;②流程控制语句

printf用法

printf '输出类型输出格式' 内容

输出类型:以%开头

输出类型

说明

%c ASCII字符
%s 字符串(后边内容中有几个参数就要写几个%s)
%-ns 字符串(-表示左对齐,n表示输出几个字符)
%-ni 输出整数,n代表输出几个数字
%f 小数点右边的位数
%m.nf 浮点数,m代表全部位数,n代表小数位数

 

输出特殊格式(换行、占位):以\开头

输出格式

说明

\a 输出警告声音
\b 输出退格键
\f 清除屏幕
\n 换行
\r 回车
\t 水平输出TAB
\v 垂直输出TAB

内容

内容如果是个文件,则会按行输出每行的处理结果

例子

[root@localhost ~]$ vi student.txt
ID      Name    php       Linux      MySQL       Average
1       AAA      66         66       66           66
2       BBB      77         77       77           77
3       CCC      88         88       88           88

#printf格式输出文件
[root@localhost ~]$ printf '%s\t %s\t %s\t %s\t %s\t %s\t \n’ $(cat student.txt)#printf '格式' 内容
#%s分别对应后面的参数,6列就写6个
ID      Name    php   Linux  MySQL   Average
1       AAA      66         66       66           66
2       BBB      77         77       77           77
3       CCC      88         88       88           88

4)常用参数(放在awk之后)

  • -F 指定输入时用到的字段分隔符
  • -v 自定义变量
  • -f 从脚本中读取awk命令
  • -m 对val值设置内在限制

①用法:awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

②例子

awk -F':' -v ORS="," '{$1=$1;PRINT $0}' /etc/passwd

分隔符-F:

输出结果分隔符(项与项之间)-v为ORS=",",

动作将第一行赋值给第一行,打印每一行

内容为 /etc/passwd的每一行

③awk内置变量(-v后可以跟的内容)

awk内置变量

作用

$0 目前awk读入的整行数据
$n 目前读入行的第n个字段(列)
NF 当前行拥有的字段(列)数
NR 当前awk处理的是哪一行
FS

输入文件的分隔符(和-F作用相同,-v FS=':'的作用就和-F ':'相同)。

默认的是任何空格,如果想要用其他分隔符(如":"),就需要用FS变量定义,写法 -v FS=':'

ARGC 命令行参数个数
ARGV 命令行参数数组
FNR 当前文件中的当前记录数(对输入文件起始为1)
OFMT 数值的输出格式(默认%.6g)
OFS 输出字段(列与列之间)的分隔符(默认空格)
ORS 输出记录(行与行之间)的分隔符(默认换行符)
RS

输入记录的分隔符(默认换行符)

这个变量的含义是,原文件中每条记录之间的分隔符,默认情况下是一行为一条记录,此时记录之间的分隔符就是换行符

记录:所有列构成的一个整体称为记录

字段:各列

5)awk条件

awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

条件类型

条件

说明

awk保留字 BEGIN

在awk程序开始时,尚未读取任何数据之前执行。

BEGIN后的动作只在程序开始时执行一次。

END

在awk程序处理完所有数据,即将结束时执行。

END后的动作只在程序结束时执行一次。

关系运算符 >  
<  
>=  
<=  
==  
!=  
A~B A中包含B的子字符串
A!~B A中不包含B的子字符串
正则表达式 /正则/  

 

①BEGIN

 

BEGIN是awk保留字,是一种特殊的条件类型。BEGIN的执行时机是“awk程序一开始,尚未读取任何数据之前执行”。

BEGIN定义的动作只能被执行一次(执行一次意思是不再像普通语句那样对每行都循环执行,而是在所有行之前执行一次就结束):

awk 'BEGIN{printf "This is a transcript \n" } {printf $2 "\t" $6 "\n"}’ student.txt
#awk命令只要检测不到完整的单引号不会执行,所以这个命令的换行不用加入“|”,就是一行命令
#这里定义了两个动作
#第一个动作使用BEGIN条件,所以会在读入文件数据前打印“这是一张成绩单”(只会执行一次)
#第二个动作会打印文件的第二字段和第六字段

 

②END

 

也是awk保留字,但和BEGIN相反。END是awk程序处理完所有数据,即将结束时执行一次(不管这个END及其后的语句放在哪里,都是在最后一行执行完毕后执行):

awk 'END{printf "The End \n"} {printf $2 "\t" $6 "\n"}’ student.txt
#在输出结尾输入“The End”,这并不是文档本身的内容,而且只会执行一次

 

③关系运算符

 

例①平均成绩大于等于87分的学员是谁

cat student.txt | grep -v Name | awk '$6 >= 87 {printf $2 "\n"}'
#使用cat输出文件内容,用grep取反包含“Name”的行
#判断第六字段(平均成绩)大于等于87分的行,如果判断式成立,则打第六列(学员名$2)

加入条件之后,只有条件成立,动作才会执行。通过这个例子可以发现:awk虽然是列提取指令,但是要按行来读入。它的执行过程如下:

  1. BEGIN条件,先执行BEGIN定义的动作
  2. 如果没有BEGIN条件,则读入第一行,把第一行的数据依次赋予$0、$1、$2等变量。其中$0代表该行整体数据$1代表第一字段$2代表第二字段
  3. 根据条件判断动作是否执行。如果条件符合,则执行;否则读入下一行数据。如果没有条件,则每行都执行动作。
  4. 读入下一行数据重复上述步骤

例②查找Sc用户的平均成绩

awk '$2 ~ /Sc/ {printf $6 "\n"}' student.txt
#如果第二字段中输入包含有“Sc”字符,则打印第六字段数据
85.66

在awk中,使用"//"包含的字符串,awk命令才会查找。也就是说字符串必须用"//"包含,awk命令才能正确识别。(如上文的/Sc/

 

④正则表达式

 

如果要让awk识别字符串,必须用"//"包含,

例1

awk '/Liming/ {print}’student.txt
#打印Liming的成绩

例2:使用df命令查看分区使用情况,如何只想查看真正的系统分区的使用情况,而不想查看光盘和临时分区的使用情况:

df -h | awk '/sda[O-9]/ {printf $1 "\t" $5 "\n"}’
#查询包含有sda数字的行,并打印第一字段和第五字段

6)awk常用实例统计

awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

1、第一列
 awk '{print $1}' filename
2、前两列(用空格分隔)
 awk '{print $1,$2}' filename
3、打印完第一列,然后打印第二列(无分隔)
awk '{print $1 $2}' filename
#2和3的区别在于,2的列与列输出时会有间隔,3的列与列在输出时直接拼在一块
4. 最后一列
awk '{print $NF}'#$NF:最后一列的值;NF:列数
5、总行数 总列数: awk 'END{print NR}' filename
awk 'END{print NF}' filename
总列数(由于行与行的列数可能不同)
因此这里输出的是最后一行的列数
1、第一行 : awk 'NR==1{print}' filename 2. 第一行的1、2、3列 ps -aux | grep watchdog | awk 'NR==1{print $1, $2, $3}' 3. 指定分隔符(这里以:分割) awk -F':' '{print $1}'
9. 超出范围不报错 ps -aux | grep watchdog | awk '{print $100}'

 

7)awk定义和调用变量(放在动作{}块中)

awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

假设我想统计PHP成绩的总分,可以写为:

[root@localhost ~]$ awk 'NR==2 {php1=$3}
NR==3 {php2=$3}
NR==4 {php3=$3;total=phpl+php2+php3;print "total php is " total}' student.txt
#统计PHIP成绩的总分

对这个命令的解释:

  • 'NR==2 {php1=$3}':如果输入数据是第二行(第一行是标题行),就把第二行的第三字段的值赋予变量php1;

  • 'NR==3 {PHP2=$3}':同上

  • 'NR==4 {php3=$3;total=php1+php2+php3;print "total php is " total}' student.txt:如果输入数据是第四行,就把第四行第三字段的值赋予php3,之后定义变量total的值为"php1+php2+php3",然后输出"total php is",后边加上变量total的值。

在awk编程中,因为命令语句很长,在输入格式时需要注意的内容:

  • 多个条件 {动作}(即上文的NR==2 {...} 与 NR==3 {...})可以用空格分割,也可以用回车分割
  • 在一个动作(即上段代码的{...}部分)中,如果需要执行多个命令(即上文的php3=$3;total=...;),需要用";"分割,或者用回车分割
  • 在awk中,变量的赋值调用不需要加入$符(只有在调用某行的哪列时才会用到$n)(有点类似Python的语法)

8)流程控制(用if的话要放在动作块{}中,不用if就直接写成相应条件

awk 参数1 变量1  参数2 变量2 '条件1 {动作1} 条件2 {动作2}' 文件/内容

之前所说awk用法:awk '条件1{动作1} 条件2{动作2}...' 文件

这里的流程控制是放在动作中(即大括号{}内)的

awk '{if (NR>=2) {if ($4>60) printf $2 "is a good man!\n"}}' student.txt
#程序中有两个if判断,第一个判断行号大于2,第二个判断Linux成绩大于90分
Liming is a good man !
Sc is a good man !

注意

awk{动作}中的if判断语句,完全可以利用awk自带的条件取代,刚刚的脚本可以写作:

awk ’NR>=2 {test=$4}
test>90 {printf $2 "is a good man! \n"}’ student.txt
#先判断行号如果大于2,就把第四字段赋予变量test
#在判断如果test的值大于90分,就打印好男人
Liming is a good man! Sc is a good man!

8)awk函数

 

定义(在动作块{}中)

 

function 函数名 (参数列表) {
    函数体
}
#实际写法
awk 'function 函数名(参数列表){
  函数体
}
{调用该函数}
' 文件/内容

 

例子

#定义函数test,包含两个参数,函数体的内容是输出这两个参数的值
awk 'function test(a,b) { printf a "\t" b "\n"}
{ test($2,$6) } ' student.txt
#调用函数test,并向两个参数传递值。 Name Average AAA 87.66 BBB 85.66 CCC 91.66

 

 

9)awk调用外部脚本文件

对于小的单行程序而言,将脚本作为命令行自变量传递给awk是非常简单的,而对于多行程序就比较难以处理。当程序多行时,使用外部脚本是很适合的。

首先在外部文件写好脚本,然后可以使用awk的-f选项,使其读入脚本并且执行。

例子

首先写好一个awk脚本

vi pass.awk
BEGIN {FS=":"}
{ print $1 "\t"  $3}

然后可以使用"-f"调用这个脚本

[root@localhost ~]$ awk -f pass.awk /etc/passwd
rooto
bin1
daemon2
…省略部分输出…