专栏首页york技术分享awk 使用教程 - 通读篇(30分钟入门)
原创

awk 使用教程 - 通读篇(30分钟入门)

awk

导言

很多刚接触awk,sed等命令时,看到帮助文档一堆参数,一堆符号感觉有点慌,我刚开始学习时也出现过这样的问题,这篇文章从我们工作遇到的问题出发,由浅入深,重点在于阐述其工作原理和最常用的用法(覆盖我们工作80%的就很满意了),作为通读性强的文章希望能利用上下班的时间就能看懂,树立一个awk能帮我们解决哪些问题的意识。当然高级用法可以基本本篇给的思路去摸索,另外会不定期的更新使用的例子。

简介

awk工作流程和原理 awk使用例子积累

面向

有用过有点迷糊想系统学习的朋友,完全没用过的朋友

修改:

awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,它在命令行中使用,但更多是作为脚本来使用。

awk语法说明

语法形式

awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file(s)

常用命令选项

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

语法结构

awk是由patternaction组成, pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令.

awk '{pattern + action}' {filenames}

pattern 可以是如下几种或者什么都没有(全部匹配):

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

action 由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,可以是如下几种,或者什么都没有(print)

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

awk常见应用和工作原理

下面列出一个最常用的awk命令结构,借此分析原理

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
  • 首先执行 BEGIN {commands} 内的语句块,注意这只会执行一次,经常用于变量初始化,头行打印一些表头信息,只会执行一次,在通过stdin读入数据前就被执行;
  • 从文件内容中读取一行,注意awk是以行为单位处理的,每读取一行使用 pattern{commands} 循环处理 可以理解成一个for循环,这也是最重要的部分;
  • 最后执行 END{ commands } ,也是执行一次,在所有行处理完后执行,一帮用于打印一些统计结果。

第一个例子,获得/etc/passwd文件种每行的地1个和第7个数据,以逗号分隔,并再第一行和最后一行打印一串文字。

shell> cat /etc/passwd |awk  -F ':'  'BEGIN {print "name,shell"}  {print $1","$7} END {print "blue,/bin/nosh"}'
name,shell
root,/bin/bash
daemon,/usr/sbin/nologin
bin,/usr/sbin/nologin
sys,/usr/sbin/nologin
...
blue,/bin/nosh

书写注意事项

  • awk后的命令需要用 单引号括起来
  • 最好用 ‘{}’ 括起来每个部分,便于阅读;
  • 每个 ‘{}’ 可以有多个命令或者其它,之间用 ‘;’ 号分割。

怎么清晰的输出想要的信息?

awk的输出主要靠 print,printf 指令,这两个指令的用法和c语言中的 print,printf 一毛一样。awk处理每行时是以列为每个域,例如 print $1 就是输出第一列,print $1,$2 就是输出第1、2列,print $0 输出全部。

awk怎么区分列呢,默认是以空格区分,但是你也可以通过 -F 参数指定,例如 -F; 指定分号为分隔符,-F[;,] 指定分号和逗号为分隔符。

假设有如下一个文件 netstat.txt

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  2304 york      20   0 2244404 213596  84764 S   6.2  5.3   4:56.97 cinnamon
 12489 york      20   0   43668   3708   2984 R   6.2  0.1   0:00.02 top
     1 root      20   0  185352   5968   3972 S   0.0  0.1   0:04.35 systemd
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.05 kthreadd
     4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H
     6 root      20   0       0      0      0 S   0.0  0.0   0:00.06 ksoftirqd/0
     7 root      20   0       0      0      0 S   0.0  0.0   0:14.82 rcu_sched
    34 root      20   0       0      0      0 S   0.0  0.0   0:00.04 khungtaskd
    35 root      20   0       0      0      0 S   0.0  0.0   0:00.00 oom_reaper
    36 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 writeback
    37 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kcompactd0

1. 我只想打印第一列和第四列的内容

shell> awk '{print $1,$2}' top.txt 
PID USER
2304 york
12489 york
1 root
2 root
4 root
6 root
7 root
34 root
35 root
36 root
37 root

2. 对齐不整齐,打印的更漂亮些?能加上行号?

来看看格式化输出 printf

shell> awk '{printf "%-8s %-8s %-8s %-18s\n",NR, $1,$2,$12}' top.txt
1        PID      USER     COMMAND           
2        2304     york     cinnamon          
3        12489    york     top               
4        1        root     systemd           
5        2        root     kthreadd          
6        4        root     kworker/0:0H      
7        6        root     ksoftirqd/0       
8        7        root     rcu_sched         
9        34       root     khungtaskd        
10       35       root     oom_reaper        
11       36       root     writeback         
12       37       root     kcompactd0

上面的对齐方式是不是更好看,命令里面的 %-8s 用过c语言输出的一定很眼熟,另外看到一个新的东西 NR 这是awk内部提供显示行号的变量,除了这些还有以下常用的,(下面这张表就是用awk处理的)

变量

说明

ARGC

命令行参数的数目

ARGIND

命令行中当前文件的位置(从0开始算)

ARGV

包含命令行参数的数组

CONVFMT

数字转换格式(默认值为%.6g)

ENVIRON

环境变量关联数组

ERRNO

最后一个系统错误的描述

FIELDWIDTHS

字段宽度列表(用空格键分隔)

FILENAME

当前输入文件的名

FNR

同NR,但相对于当前文件

FS

字段分隔符(默认是任何空格)

IGNORECASE

如果为真,则进行忽略大小写的匹配

NF

表示字段数,在执行过程中对应于当前的字段数

NR

表示记录数,在执行过程中对应于当前的行号

OFMT

数字的输出格式(默认值是%.6g)

OFS

输出字段分隔符(默认值是一个空格)

ORS

输出记录分隔符(默认值是一个换行符)

RS

记录分隔符(默认是一个换行符)

RSTART

由match函数所匹配的字符串的第一个位置

RLENGTH

由match函数所匹配的字符串的长度

SUBSEP

数组下标分隔符(默认值是34)

3. 打印出的信息不够,我要计算结果

例如上面的例子,我想统计出所有进程总共占了多少cpu,awk变量和基本运算 了解一下,先看例子

shell> awk 'BEGIN {sum=0} {printf "%-8s %-8s %-18s\n", $1, $9, $11; sum+=$9} END {print "cpu sum:"sum}' top.txt
PID      %CPU     TIME+             
2304     6.2      4:56.97           
12489    6.2      0:00.02           
1        0.0      0:04.35           
2        0.0      0:00.05           
4        0.0      0:00.00           
6        0.0      0:00.06           
7        0.0      0:14.82           
34       0.0      0:00.04           
35       0.0      0:00.00           
36       0.0      0:00.00           
37       0.0      0:00.00           
cpu sum:12.4

又看到几个新的东西,变量初始化及awk的一些基本运算

  • sum=0 一般都在 BEGIN 里面初始化一个变量,如果不需要初始化可以直接进行对变量的赋值,这很像脚本语言中的自动推断,除了提供基本的运算以外(有哪些?c有哪些这就基本有哪些),还提供了一些高级计算函数,例如数学运算log、sqr、cos、sin, 字符运算 length、substr
  • 引入 awk变量 除了能在内部使用,还可以从外部引入,使用 -v 参数指定,例如下面是打印环境变量的例子,这个在编写脚本中很常见。 awk -v var=$PATH 'BEGIN {print var}' top.txt

上面的例子引入了 awk 的运算,知道可以定义变量运算,除此之外还支持很多运算符,算术运算符,逻辑运算符,甚至一些内部提供的函数。

信息太多,我需要筛选

前面有说到awk是由 patternaction 组成,其中 pattern 部分就是能帮我们匹配或者过滤掉一些信息,过滤方式有很多,比如条件判断,正则匹配,甚至还可以和c语言一样写 if else, 到此就不要把 awk 当命令了,它就是一门语言,后面还有更高级的用法。

1. 去掉第一行,只输出cpu消耗大于0的

shell> awk 'NR>1 && $9>0 {printf "%-8s %-8s %-18s\n",$1,$9,$12}' top.txt 
2304     6.2      cinnamon          
12489    6.2      top 

首先按照上面所介绍的 awk 执行流程来介绍,单引号里面的 模式+命令 在每读到一行时就会执行,判断条件也是如此 NR>1 && $9>0 这种写法和c语言没有两样,只是少了判断 if 而已,每读到一行时都执行这个判断条件来确定是否过滤;下面转换成高级语言的代码。

for l in lines {
	if ( NR>1 && $9 >0 ) {
		printf ("%-8s %-8s %-18s\n",$1,$9,$12);
	}
}

注意:这个 NR>1 经常在数据库查询后被使用,来剔除表头。

这个例子里面出现的就是 awk 的条件判断,条件判断运算符也是和c语言一样不多阐述,在比较时不仅可以比较数字还可以比较字符串,awk会自动识别,比较字符串时会按照ASCII码顺序比较。

2. 保留表头,只输出特定用户名的进程

awk 'NR==1 || /york/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt 
PID      USER     %CPU     COMMAND           
2304     york     6.2      cinnamon          
12489    york     6.2      top               

上面 pattern 出现了一个新语法 /york/ ,这个就是正则匹配,面对一些字符串匹配来进行过滤,通过运算符显的很无力,这在处理大量log时尤为突出,awk 也想到这点,支持正则匹配来精准筛选;正则过滤有好几种运用方法,但主要格式都是 在双斜杠内写上你的正则表达式;例如上面的例子就是 该行只要出现 ‘york’ 字符即可满足过滤条件

3. 上面例子不太准确,更好的做法是针对域匹配

上面例子表述的是 改行只要出现 即可匹配到,万一不是 USER 字段也有 ‘york’ 字符就会出现错误;

shell> awk 'NR==1 || $2~/york/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt 
PID      USER     %CPU     COMMAND           
2304     york     6.2      cinnamon          
12489    york     6.2      top 

上面引入了 ~ 运算符,该运算符和 !~ 成对立关系,类似 =!=的关系,前者表示匹配到的输出,后者表示匹配到的过滤。假设过滤 ‘york’ 的输出,可以这样写:

shell> awk 'NR==1 || $2!~/york/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt 
PID      USER     %CPU     COMMAND           
1        root     0.0      systemd           
2        root     0.0      kthreadd          
4        root     0.0      kworker/0:0H      
6        root     0.0      ksoftirqd/0       
7        root     0.0      rcu_sched         
34       root     0.0      khungtaskd        
35       root     0.0      oom_reaper        
36       root     0.0      writeback         
37       root     0.0      kcompactd0  

另外要注意的就是针对域匹配 $2~/york/, 前面有说过 $0 代表整个域,所以 $0~/york//york/ 是等价了,说这个的原因就是当我们需要 针对一行做正则过滤的时候可以这样写 $0!~/york/, 这个在过滤日志的时候非常重要。下面增加几个例子

# 输出打印一行中出现 k 字符的行
awk 'NR==1 || /k/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt
awk 'NR==1 || $0~/k/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt

# 过滤掉一行中出现york字符的行
awk 'NR==1 || $0!~/york/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt

# 输出某个域字符以 k 开头的行
 awk 'NR==1 || $12~/^k/ {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}' top.txt

根据条件定制我的输出流程

信息过滤针对的是 pattern 做文章,那么后面的 action 是不是也可以定制化控制,答案肯定可以,action 可以做流程定制,就是说我可以在里面使用 if else while 等流程控制语句。这和上面的条件判断不一样,因为他们针对的是不同部分,前面用于信息过滤,后面用于流程控制。

1. 过滤cpu大于0的,我还可以这样写

awk '{if($9>0){printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12}}' top.txt
2304     york     6.2      cinnamon          
12489    york     6.2      top    

条条大路通罗马不是吗?这个例子就是流程控制的一个简单运用,利用到的是awk的流程控制,下面是流程控制的语法结构,了解下:

if (expression) {
    statement;
    statement;
    ... ...
}

if (expression) {
    statement;
} else {
    statement2;
}

if (expression) {
    statement1;
} else if (expression1) {
    statement2;
} else {
    statement3;
}

上面的例子在扩展下,在结尾并统计下cpu的总和

awk 'BEGIN {sum=0} {if($9>0){printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12; sum+=$9}} END {printf "cpu total usaged: %s\n", sum}' top.txt
2304     york     6.2      cinnamon          
12489    york     6.2      top               
cpu total usaged: 12.4

2. 统计计算,输出各个USER的进程数

shell> awk 'NR!=1{a[$2]++;} END {for (i in a) print i ", " a[i];}' top.txt
york, 2
root, 9

这个例子用一个数组统计不同用户的进程个数,并在最后用循环打印出来,这里有两个新的概念,一个是另外一种流程控制循环,另一个是数组的使用。关于循环的控制语法如下,和其它高级语言都类似。

while(表达式)
  {语句}
for(变量 in 数组)
  {语句}
for(变量;条件;表达式)
  {语句}
do
{语句} while(条件)

除此之外,流程控制还有 break, continue, exit 等关键字,这些关键字含义和c语言一毛一样。

上面例子中 a[$2] 是典型的一种数组使用方法,用编程语言来看,这个叫数组似乎不大妥当,理解成 map 更合适,更像是 key-value 的存储结构。

3. 怎样使用数组

上面看到了数组的基本使用,其实 awk 给数组赋予了很多功能,和很多高级脚本语言一样,提供了相关的函数获取长度,排序等,另外存储是 key-value 结构,能像map一样判断key是否存在。

获取长度 length

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

上面 split 函数用于字符串分割,和c语言的又是一毛一样

循环输出

shell> awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'
4 test
1 it
2 is
3 a 

由于是 key-value 的存储结构,所以使用这样的for循环输出结果是无序的,

有序输出

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

注意:数组下标从1开始的 注意:这种输出方法仅适用于把数组真正当作 ‘数组’ 使用,key值就是自然递增的数,而不是当map

判断是否存在 key in array

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

注意:判断语法是 key in array 不能直接写 array[key] != value 形式,如果这样写它会默认创建一个,使用过高级脚本语言的都知道;

删除 deletekey

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

搞掂,保存下来

这里引入怎么将处理后的文本保存下来,awk 提供了重定向的的功能;

1. 将上面例子中cpu大于0的保存到cpu.txt文件

shell> awk 'BEGIN {sum=0} {if($9>0){printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12 > "cpu.txt"; sum+=$9}} END {printf "cpu total usaged: %s\n", sum > "cpu.txt"}' top.txt

shell> cat cpu.txt
2304     york     6.2      cinnamon
12489    york     6.2      top
cpu total usaged: 12.4

上面引入的 > 就是重定向符,使用方法很简单,在你想要输出到文件的 print 命令后加上 > "filename" 即可。

2. 提点小要求,拆分文件存储,按USER拆分

shell> awk 'NR>1 {printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12 > $2}' top.txt
shell> cat york
2304     york     6.2      cinnamon          
12489    york     6.2      top  
shell> cat root
1        root     0.0      systemd           
2        root     0.0      kthreadd          
4        root     0.0      kworker/0:0H      
6        root     0.0      ksoftirqd/0       
7        root     0.0      rcu_sched         
34       root     0.0      khungtaskd        
35       root     0.0      oom_reaper        
36       root     0.0      writeback         
37       root     0.0      kcompactd0 

上面按照 ‘USER’ 简单做了文件拆分,将输出内容拆分到 ‘york’和‘root’ 两个文件中,这个技巧在后面数据归类或者日志归类中使用非常频繁。

3. 要求在高点,根据字符匹配来确定文件拆分 (结合if-else语句)

shell> awk 'NR>1 {if($0~/york/){printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12 > "1.txt"}else if($0~/root/){printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12 > "2.txt"}else{printf "%-8s %-8s %-8s %-18s\n",$1,$2,$9,$12 > "3.txt"}}' top.txt 

上面将生成3个文件,其中USER=york的在‘1.txt’,USER=root在‘2.txt’,其它在‘3.txt’, 上面的例子就是前面所介绍的一种结合,正则匹配其实也可以用在 action 中做流程判断。

附录:实例记录

最后了解原理和过程后,发现一切都是水到渠成,其实不然,配合其它命令(比如管道,排序等)可以实现很多不可思议的工作,这里专门积累一些平时用到的。

#按连接数查看客户端IP
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr

(欢迎各位对文章中的错误指正,另外如果小伙伴们有好的实例来分享,谢谢各位)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • sed 使用教程 - 通读篇(30分钟入门系列)

    和上篇 awk 分享一样,作为通读性的分享,不想引入太过复杂的东西,依然从日常工作中碰到的 80% 的需求出发,重点阐述最重点的部门,工作原理等,普及一些对se...

    yorkjin
  • linux AWK学习

    葫芦
  • linux awk命令详解

    简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以...

    千往
  • 10分钟学会 linux awk命令

    ? 简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读...

    小小科
  • linux awk命令详解

    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为...

    流柯
  • 10分钟学会 linux awk命令

    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为...

    马哥linux运维
  • awk 基础入门

    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为...

    RainMark
  • 10分钟学会 linux awk命令

    awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为...

    小小科
  • 生物信息 awk 用法进阶

    全文6,829字(含代码),阅读18分钟。配图来源:《The AWK Programming Language》

    黄树嘉
  • Awk入门学习

    由于最近的工作内容的关系,经常需要对文本文件做一些处理。每次都要写个脚本来处理实在是有点麻烦。这时候想起来很久以前稍微接触过的 AWK, 来做这个工作真的是再合...

    呼延十

扫码关注云+社区

领取腾讯云代金券