linux awk 实战用法

时间:2022-9-2     作者:smarteng     分类: Linux命令


近期有一个新闻很有意思,80岁的计算机领域泰斗 Brian Kernighan 大神,也就是我们常说的 K & R 中的 K,依然在为 awk 贡献代码增加了对 Unicode 的支持,详细的背景和采访请看原文

说来惭愧,入行这些年有意无意地用过多次 awk,但总是用了忘,忘了再查,一直没有系统梳理过。正好借这个机会,review 一下 awk 这个经典工具的定位和常见用法,类似的还有 grep, sed, sort, curl 等。

今天我们先来复习一下 awk

awk

在 Unix-like 的系统中,awk 是一个自带的工具,它通常被用来数据过滤,文本处理,和 sed 以及 grep 类似,它本质是一个 filter 过滤器。

awk 工具定义了一套自己的脚本语言,通过一系列 action 来对文本数据进行处理。感兴趣的话,完整的文档大家可以参考 The GNU Awk User's Guide

awk 不需要编译,允许用户使用变量,数值函数,字符串函数,逻辑运算符。有了它的帮助,开发者可以写出很精巧但又有效的脚本,定义一套文本查找模式,以及找到之后要执行的 action。

很多同学比较感兴趣,这样一个工具为什么叫 awk。其实它的意义是三位开山之祖的名字:

Aho, Weinberger, Kernighan

能力剖析

我们能用 awk 来做什么事呢?

  1. 一行一行地扫描文件内容;

  2. 将输入的一行数据 split 成多个 field;

  3. 对输入数据进行模式匹配;

  4. 针对匹配的行做一些操作(action)。

基本语法

awk 命令的格式如下:

awk options 'selection _criteria {action }' input-file > output-file
复制代码

事实上如果大家忘记了,直接在自己的 mac 或者 linux 上运行 awk,就会出现帮助提示:

$ awk

usage: awk [-F fs] [-v var=value] [-f progfile | 'prog'] [file ...]
复制代码

这里提供了两个选项

-f program-file : Reads the AWK program source from the file 
                  program-file, instead of from the 
                  first command line argument.
-F fs            : Use fs for the input field separator
复制代码

实战用法

假定我们有一个 employee.txt 文件,内容如下:

ajay manager account 45000
sunil clerk account 25000
varun manager sales 50000
amit manager account 47000
tarun peon sales 15000
deepak clerk sales 23000
sunil peon sales 13000
satvik director purchase 80000 
复制代码

awk 默认会打印指定文件里所有行:

$ awk '{print}' employee.txt
复制代码

此时因为没有提供需要匹配的模式,所以这个 print action 对于所有行都适用,并且 print action 如果没有参数就会默认打印一行里的所有内容。

所以运行上面命令,会打印出来原本的文本内容

ajay manager account 45000
sunil clerk account 25000
varun manager sales 50000
amit manager account 47000
tarun peon sales 15000
deepak clerk sales 23000
sunil peon sales 13000
satvik director purchase 80000 
复制代码

ok,如果只是这样就太鸡肋了,我们的 employee.txt 的特征还是很明显的,可以分析出来文本的第一列是员工姓名,第二列看起来是职位。

现在我们希望过滤出来所有 manager,可以这样运行:

$ awk '/manager/ {print}' employee.txt 
复制代码

此时会打印

ajay manager account 45000
varun manager sales 50000
amit manager account 47000 
复制代码

前面是匹配的模式,后面 {} 中的则是具体的 action。

除了模式能够调整外,action 也是可以调整的,如果我们不希望用 print 默认的行为,现在想将一行 split 成多个 field 怎么办?

awk 默认会根据空格来 split 每一行数据,并将每个 field 存储在 n这些变量中。比如一行有4个单词,那么分隔后的结果将会被存储在n 这些变量中。比如一行有 4 个单词,那么分隔后的结果将会被存储在 n这些变量中。比如一行有4个单词,那么分隔后的结果将会被存储在1, 2,2, 2,3, 4这四个变量中。注意,4 这四个变量中。注意,4这四个变量中。注意,0 代表了整个行。

所以,如果我们想基于 employee.txt 打印出来各个员工的名字和薪资(最后一列),可以这样做:

$ awk '{print $1,$4}' employee.txt 
复制代码

此时会打印出来:

ajay 45000
sunil 25000
varun 50000
amit 47000
tarun 15000
deepak 23000
sunil 13000
satvik 80000 
复制代码

第一列,第四列,完美符合预期。

除了上面我们提到的 $n 这些变量外,awk 还提供了一些内置的变量供开发者使用:

NR

NR command keeps a current count of the number of input records. Remember that records are usually lines. Awk command performs the pattern/action statements once for each record in a file.

NR 即 Number of Record, 记录了当前已经计数过的 record(行)数量。比如下面的命令:

$ awk '{print NR,$0}' employee.txt 
复制代码

此时会你会看到前面加上了行号:

1 ajay manager account 45000
2 sunil clerk account 25000
3 varun manager sales 50000
4 amit manager account 47000
5 tarun peon sales 15000
6 deepak clerk sales 23000
7 sunil peon sales 13000
8 satvik director purchase 80000 
复制代码

这里我们也可以加一些分隔符,比如打印编号 + 第一列,用 - 来隔开,就可以这样:

$ awk '{print NR "-" $1 }' employee.txt
复制代码

得到的输出如下:

1-ajay
2-sunil
3-varun
4-amit
5-tarun
6-deepak
7-sunil
8-satvik
复制代码

当然,我们还可以活用 NR 来输出指定行:

$ awk 'NR==3, NR==6 {print NR,$0}' employee.txt 
复制代码

这样的命令代表了我们要打印 3 - 6 行这个区间内的行,输出如下:

3 varun manager sales 50000
4 amit manager account 47000
5 tarun peon sales 15000
6 deepak clerk sales 23000 
复制代码

有时候我们想打印出来一个文件的行号,就可以取最后一行的 NR,此时所有行都计数过了,所以 NR 等价于总行数:

$ awk 'END { print NR }' employee.txt 
复制代码

这里运行结果为 8,符合预期。

NF

NF command keeps a count of the number of fields within the current input record.

NF 即 Number of Field,记录了当前输入的 record 列的数量,我们可以用 $NF 来代表最后一列。

$ awk '{print $1,$NF}' employee.txt 
复制代码

运行之后,我们看到此时打印的是第一列和最后一列

ajay 45000
sunil 25000
varun 50000
amit 47000
tarun 15000
deepak 23000
sunil 13000
satvik 80000 
复制代码

NR + NF

现在我们有了 NR 和 NF,可以联系起来做什么呢?

比如此时我们希望找到空行的行号,假设有一些行就是没数据,是空的,怎么打印?

为了测试,我在第五行加了个空行,变成了这样:

ajay manager account 45000
sunil clerk account 25000
varun manager sales 50000
amit manager account 47000

tarun peon sales 15000
deepak clerk sales 23000
sunil peon sales 13000
satvik director purchase 80000
复制代码

其实很简单,空行的 NF 一定为 0,打印行号可以用 NR,所以我们可以这样:

$ awk 'NF==0 {print NR}' employee.txt
复制代码

运行后结果是 5,符合预期。

length

awk 提供了 length 函数计算字符串长度,比如我们希望找到所有比 80 个字符还长的行,可以这样:

awk 'length($0) > 80' employee.txt
复制代码

可能有些同学不理解,这里为啥不加 print 呀?

其实大家可以试试,不加也能实现打印,因为 default action 就是 print。

用 if 语句作为 pattern

有时候我们希望根据某个列的值进行匹配,假设我们希望找到第三列的值等于 sales 的行,可以这样:

$ awk '{ if($3 == "sales") print $0;}' employee.txt
复制代码

运行之后打印结果:

varun manager sales 50000
tarun peon sales 15000
deepak clerk sales 23000
sunil peon sales 13000
复制代码

数值计算

有时候我们不一定有一个 source file,而是直接想在 for 循环里自己构造条件,可以参考这个例子:

$ awk 'BEGIN { for(i=1;i<=6;i++) print "square of", i, "is",i*i; }' 
复制代码

这里你会发现我们没有提供 source file 参数,在 pattern 里直接提供了一个 for 循环,对 i = 1 到 6 这个区间内,都去打印后面几个值。输出结果:

square of 1 is 1
square of 2 is 4
square of 3 is 9
square of 4 is 16
square of 5 is 25
square of 6 is 36
复制代码

BEGIN 和 END 两个规则是没有默认 action 的,我们必须提供显式的 action print 才行。

小结

今天我们回顾了下 awk 的基础概念,结合一些实战案例了解了 awk 常见的用法,这些远不是全部,awk 提供了完整得一套语言来进行文本处理。

我们这里列举的只是冰山一角,建议大家感兴趣的话参照 User Guide 深入学习一下,对文本处理会有很大好处。

awk --re-interval '{match($0,/([0-9]{1,3}.){3}[0-9]{1,3}/,a); print a[0]}'
awk正则命令获取ip

标签: awk