Linux 文本行处理神器 awk

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

awk

AWK是一种处理文本文件的语言。它将文件作为记录序列处理。

在一般情况下,文件内容的每行都是一个记录。每行内容都会被分割成一系列的域,因此,我们可以认为一行的第一个词为第一个域,第二个词为第二个,以此类推。

AWK程序是由一些处理特定模式的语句块构成的。AWK一次可以读取一个输入行。对每个输入行,AWK解释器会判断它是否符合程序中出现的各个模式,并执行符合的模式所对应的动作。

——阿尔佛雷德·艾侯,The A-Z of Programming Languages: AWK

awk命令的通用语法格式

awk '{commands}'
复制代码
  • commands 为一个或多个命令
  • 常用选项:
    • -f,表明awk命令将从该标志之后的文件中读取指令而不是从命令行读取;
    • -Fc,这个标志表明字段之间的分隔符是c而不是默认的空白字符(如制表键、一个或多个空格符)。

常用点:

  • print:将一行接一行地打印出文件中的所有数据行。

可用 print str1 str2 int1,int2...

也可 print(A,B,C,...)

$ who | awk '{ print }'   # 这个就等于直接使用「who」
复制代码

如果要打印 \n, \t 等字符:

$ ls -lF /boot | awk '{ print $5 "\t" $9}' | sort -rn | head -3
复制代码

(这行命令的作用是:列出 /boot 目录中每一个文件的文件名和大小,文件的大小在前,而文件名随后,文件大小和文件名由制表键隔开,按大小反序排列(从大到小)把其中最大的3个文件输出出来。利用到了下文的 $n 用法,后面会详细介绍)

awk 中也可以用 c 风格的 printf():

printf("Total: %s\n",totalsize)    # 注意'\n'
复制代码

$n:字段变量

在文件和Linux命令的结果显示中,每行信息被指定的分隔符分隔成若干个字段,每个字段都被赋予一个唯一的标识符。

如,字段1的标识符是 $1,字段2的标识符是 $2 等。特殊地,有变量 $0 表示整行 ($0=re('^\*$'))。

awk 中,会大量使用到这种字段标识,下面是一些例子:

  1. 列出who命令显示结果中每行的第1个字段,即目前登录Linux系统的用户名

    $ who | awk '{ print $1 }'
    复制代码
  2. 在上一个命令的基础上加入一些解释性的话:

    $ who | awk '{ print "User  " $1 " is on terminal line " $2}'
    复制代码
  3. emp.data 文件中的第2个字段为员工姓,第4个字段为工资。在员工姓前加上Employee,在员工的姓和工资之间加上 has salary 字符串,再输出:

    $ awk '{ print "Employee  " $2 " has salary " $4}' emp.data
    复制代码
  4. /etc/passwd 中所有的字段都是以 : 分隔的。现在要获取某些用户(以 foo 与 bar 为例)登录时使用的shell,可以使用如下步骤:

    • 1). 可以考虑用 grep 从 /etc/passwd 文件中抽取包含目标用户 foobar 的数据行;
    • 2). 用 awk 命令把冒号看成字段的分隔符并将列出第1个(用户名)和第7个字段(登录时的shell);
    • 3). 为了方便阅读,在显示结果中加入一些描述信息以帮助阅读和理解;

    具体实现如下:

    $ egrep 'foo|bar' /etc/passwd | awk -F: '{ print $1" has " $7 " as loggin shell." }'
    复制代码
  5. 获取系统上各个shell分别被几个用户默认使用:

    $ awk -F: '{ print $7 }' /etc/passwd | sort | uniq -c
    复制代码
  6. 获取哪些用户在登录时使用的 shell 是存放在 /bin 目录中以及这个 shell 的名字;同时对于登陆使用 /bin/sync 的 sync 用户,不让它出现在显示的结果;最后进行排序。

    $ grep /bin/ /etc/passwd | awk -F: '{ print $1" " $7 }' | sed '/sync/d' | sort
    复制代码
  7. 在每个用户记录的最前面显示这个用户登录Linux系统所使用的计算机(who 命令显示结果中的最后一个字段是用户登录 Linux 系统所使用的计算机的 IP 地址,如果为空表示是本机登录)

    $ who | awk '{ print $6": "$0}'
    复制代码

NFNR变量

NF$NF

  • NF (没有 $ 符号的NF变量):表示一行记录中有多少个字段

  • $NF(带有$符号的NF变量): 一行记录中的最后一个字段(即,第NF个字段,也就是最后一个字段)

e.g.

  1. 列出who命令显示结果中每一行的字段数(列数):

    $ who | awk '{ print NF }'
    复制代码
  2. 列出who命令显示结果中每一行的最后一个字段:

    $ who | awk '{ print $NF }'
    复制代码
  3. 一个复杂的例子:

    $ egrep 'bin|sbin' /etc/passwd | awk -F: '{ print $NF }' | sort | uniq -c | sort -n
    复制代码
    • egrep:从/etc/passwd文件中抽取包含bin或sbin的数据行
    • awk:把冒号看成字段的分隔符,列出每一行的最后一个字段
    • sort:将那些字段进行排序
    • uniq -c:合并相同行,并在每行前面冠以该行出现次数
    • sort -n:按次数的大小进行排序。

NR

变量 NR 用来追踪所显示的数据行的数目,即显示数据行的编号

例如:

$ ls -l ~/wolf | awk '{ print NR": "$0}'        # 用NR给每行一个编号
1: total 16
2: drwxrwxr-x  2 dog dog 4096 Jan 25  2009 boywolf
3: -rw-rw-r--  1 dog dog   84 Dec 22 19:07 delete_disable\
......
复制代码

awk中计算

awk 可以使用 C 语言的运算符、if条件、for循环。但变量使用前不用声明。

e.g.

获取 /boot 目录中所有文件大小的总和(将得到累加过程中每一步的 totalsize 值):

$ ls -lF /boot | awk '{ totalsize += $5; print totalsize }'
复制代码

我们只需要最终结果,不输出中间值,可以用 tail 取最后一个:

$ ls -lF /boot | awk '{ totalsize += $5; print totalsize }' | tail -1
复制代码

但除了使用 tail 命令之外,一种更好的方法是在 awk 命令中使用 END 关键字。

END 关键字

在最后一步执行 END 后的 {statements}

$ ls -lF /boot | awk '{ totalsize += $5} END { print totalsize }'
复制代码

if 条件、for 循环

使用 if 的例子:

$ awk  '{ if (length($4) == 3  ) print $0 }' emp.data | wc -l
复制代码

使用 for 循环和 if 类似,直接写 for(i=0; i < 10; i++) 这样的即可。

commands表达式放入文件

前面看到了,awk 的 commands 可以写的很复杂,基本上是一个程序了,所以我们可以把它放到一个文件中:

用你喜欢的编辑器把 commands 写完,保存在例如 script1 文件中。然后就可以用 awk 的 -f 来从文件中读取 commands:

$ ls -lF /boot | awk -f script1
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享