文本处理命令awk 实例学习参考

awk.jpeg

文章源地址
awk命令是Linux系统里非常强大和功能丰富的文本行处理工具命令,使用方法简单,但是功能丰富,处理文本的速度也非常快。awk是Linux及Unix环境中现有的功能最强大的数据处理引擎之一。强大到一个命令工具拥有了自己的语言,Awk的名字来源于它的三个开发者首字母(Alfred Aho, Peter Weinberger, and Brian Kernighan), 这款软件在1977年就有了,一直保留至今足以说明其重要性了。当你遇到文本处理问题一筹莫展时,而你恰恰又看到了awk命令的使用方法介绍,那么你会在这里找到你的解决方法的。

awk语法

awk的基本使用方法如下:

pattern { action }

pattern 就是模式,用来精准的查找到你要处理的数据位置,支持数值判断,字符串比较,正则匹配等。
action 就是要对 pattern 查找到的数据行进行处理的逻辑动作,例如输出找到的行数据、统计行数或者是查找是否有你的名字等等。

awk pattern语法

模式pattern可以是以下任何任何一种格式:

BEGIN   : 内建模式,表示在处理文件之前的模式操作,我们可以在这个模式中执行一些初始化等预先的动作任务。
END     : 内建模式,表示在处理文件之后的模式操作,我们可以在这个模式中执行一些展示统计结果等输出的动作任务。
/regular expression/  : 正则表达式,支持大部分正则语法,例如 /^[0-9]/ 匹配首部包含数字的行。
relational expression : 关系表达式,例如 大于,小于,等于的比较 `$1 > 100` 匹配 第一个字段大于100的行。
pattern && pattern    : 模式与模式的逻辑与,两模式都真时匹配行。
pattern || pattern    : 模式与模式的逻辑或,两模式至少一个为真即可匹配行。
! pattern             : 模式的逻辑非, 当模式为假时匹配行, 例如 `! $1 > 10` 匹配的是第一个字段不大于10的行数据。
(pattern)             : 括号包含的模式,让模式表达的逻辑关系可以更复杂,例如 `($1~/^ABC/ && $2 > 100) || $1~/^XYZ/` ,匹配 第一列为"ABC"并且第二列大于100的行或者匹配第一列以XYZ开头的行,其实可以更复杂,不过太复杂就不太直观理解,能简单化尽量简单表达。
pattern1, pattern2    :  范围模式匹配,匹配 `pattern1`和`pattern2`之间的行数据。
pattern1 ? pattern2 : pattern3 : 三元运算,这个在Unix系统有可能不支持的,在 `gawk`和`nawk`下通常是支持的, 如果 `pattern1` 为真,则当 `pattern2`为真的行数据匹配成功,如果 `pattern1`为假,则当` pattern3` 为真时匹配行数据, 例如 `$1~/^ABC/ ? $2 > 100 : $1~/^XYZ/` ,预期类似的语法为`($1~/^ABC/ && $2 > 100) || $1~/^XYZ/` ,所以这个是可以通过其他逻辑表达的,很少用到了。

awk 内建变量

NR:已输入记录的条数。
NF:当前记录中域的个数。记录中最后一个域可以以$NF的方式引用。
FILENAME:当前输入文件的文件名。
FS:“域分隔符”,用于将输入记录分割成域。其默认值为“空白字符”,即空格和制表符。FS可以替换为其它字符,从而改变域分隔符。
RS:当前的“记录分隔符”。默认状态下,输入的每行都被作为一个记录,因此默认记录分隔符是换行符。
OFS:“输出域分隔符”,即分隔print命令的参数的符号。其默认值为空格。
ORS:“输出记录分隔符”,即每个print命令之间的符号。其默认值为换行符。
OFMT:“输出数字格式”(Format for numeric output),其默认值为"%.6g"。

awk 内建函数

awk 脚本用法

以一个hello.awk脚本文件为例,说明使用awk脚本方法

  1. awk脚本第一行添加#!/usr/bin/awk -f ,表示这个脚本需要使用 awk 命令进行解析执行;
  2. 创建脚本后,还需要给脚本文件增加可执行权限,命令为chmod +x ./hello.awk

增加完可执行权限后,我们就可以像执行命令一样执行hello.awk脚本了。

$ chmod +x hello.awk

$ ./hello.awk
Hello, world!

$ cat hello.awk
#!/usr/bin/awk -f
BEGIN { print "Hello, world!" }

如果我们hello.awk给脚本命名是 cat 的话,这与系统自带的 cat命令重名了,那么我们怎么知道这个是不是系统命令呢?

$ file ./cat
cat: awk script, ASCII text executable

当然,我们可以直接查看这个cat文件内容就可以知道它是一个awk脚本了,然而对于二进制文件的时候file命令就非常有帮助啦。

awk 实例

awk 第一个实例

第一个是Hello World!:

$ echo "Bob"|awk 'BEGIN{ print "Hello "}{ print $0} END{ print "World!"}'
Hello
Bob
World!
  • BEGIN模式是在处理所有数据之前一定要匹配的模式
  • END模式是在处理完毕所有数据之后一定要匹配的模式(常常用END进行数据展示输出)。
  • 中间的 {print $0}没有写任何模式,默认会处理所有行数据。

awk 第二个实例:模式的使用

awk '$1~/^[0-9]/ && NF > 1 {
  total_result += $1
}
END{
  printf("result: %.0f\n", total_result);
}' a.txt

awk 第三个实例:内建变量使用

awk -F'[, ]' 'BEGIN{
  ## 设置输出域分割符号为冒号:
  OFS=":"
}/^[0-9]/ && $1 > 1000{
  print FNR,NR,NF,FILENAME
}' b.txt a.txt
3:3:3:b.txt
4:4:2:b.txt
5:5:3:b.txt
6:6:3:b.txt
1:14:10:a.txt
2:15:10:a.txt
3:16:10:a.txt
4:17:10:a.txt
5:18:10:a.txt

  • BEGIN模式中设置了输出域分隔符变量OFS为“:”,之后使用print的默认域分隔符都使用OFS
  • 处理文件为两个(a.sh 和 a.txt ) , 此时对比FNRNR的差异了,NF有时候也会用来格式化过滤一些垃圾数据。

awk 第四个实例: 逻辑判断和循环控制

下面是一个统计英文单词频率的实例,使用两种for循环语法

$  cat vuln.c
#include

int main(int argc, char *argv[])
{
    char buffer[500];
    strcpy(buffer, argv[1]);
    return 0;
}

$ awk -F"[^a-zA-Z]+" '{
  for(i=1; i<=NF; ++i)
  {
    words[tolower($i)]++
  }
}
END {
    for(i in words)
    {
      if( words[i])
      {
        print i, words[i]
      }
    }
}' vuln.c
argv 2
char 2
h 1
 13
strcpy 1
buffer 2
main 1
argc 1
return 1
stdio 1
include 1
int 2

  • for循环的语法中,我比较常用的是 for( i in words)这种, 利用哈希(hash)进行数据存储和查询使用起来非常方便,也经常用到统计分析数据中。

awk 第五个实例: 结合Shell使用

这个grepinawk脚本执行起来非常类似grep -H命令:

$ cat grepinawk
pattern=$1
shift
awk '/'$pattern'/ { print FILENAME ":" $0 }' $*

$ ./grepinawk strcpy vuln.c
vuln.c:    strcpy(buffer, argv[1]);

$ grep -H strcpy vuln.c
vuln.c:    strcpy(buffer, argv[1]);

  • 这个awk脚本结合了shell脚本的参数实现了类似grep命令的功能
  • grepinawk 脚本第一个参数传递给 pattern变量 , 然后通过 shift 从参数列表中删去。
  • $* 记录的是去掉了地一个参数的参数列表了,这就达到了grep 命令的简单实现。

awk 第六个实例:awk内建函数的使用

## 统计一下本机都与哪些外部地址有TCP连接方法

netstat -tn | awk '/ESTABLISHED/{
  LocalAddress   = $4;
  ForeignAddress = $5;
  ## 按照冒号分割 ForeignAddress 字段内容
  n = split(ForeignAddress, arr , ":")
  if( n == 2)
  {
    address[ arr[1] ] ++
  }
}END{
  for( i in address)
  {
    printf("%-18s %-5.0f\n", i, address[ i ]);
  }
}'


## 方法二
netstat -tn | awk '/ESTABLISHED/{
  gsub(":", " ");
  i=$6;
  address[ i ]++
}
END{
  for( i in address)
  {
    printf("%-18s %-5.0f\n", i, address[ i ]);
  }
}'

  • 这个实例使用了split内建函数,可以将数据重新进行拆分成一个数组,然后返回值为数组数量,需要记住,数组下表是从1开始的,并不是像C语言从0开始的。
  • 方法二通过使用 gsub 函数也实现了同样效果。

awk 第七个实例: 重定向输出和管道的使用

一个awk命令可能有很多输出内容,并且需要保存到不同名称的文件中,这时候就需要使用重定向输出啦:

netstat -tn | awk '/^tcp/{
  state = $NF
  print $0 > "/tmp/test/"$NF
  printf("%-20s %-20s %-s\n", $4,$5,$6) | "sort"
}'

awk 第八个实例: 函数的使用

>>>>>>>>>>>>$ cat b.txt
100 200 300
1000 40302 2130130
12309 10201 10210
1201 102109
12031 102101 2101
10201 1010 111
10 11121
990 1024
4 101
29 4010
11 230
5 102
7 1021 1012 101

>>>>>>>>>>>>$ awk 'function add (n1, n2) {
  return n1 + n2
}
$1> 100{
  printf("%-8.0f + %-8.0f = %10.0f\n", $1 , $2 , add( $1, $2))
}' b.txt

1000     + 40302    =      41302
12309    + 10201    =      22510
1201     + 102109   =     103310
12031    + 102101   =     114132
10201    + 1010     =      11211
990      + 1024     =       2014

>>>>>>>>>>>>$

你可能感兴趣的:(文本处理命令awk 实例学习参考)