目录

第6章 利用sed/awk处理文本

磨刀不误砍柴工,如果能妥当利用sedawk这两个利器,很多日常的命令操作和文件操作会事半功倍;当然用python的os、re模块配合也能实现这样的功能,但一来不方便,二来阅读很多bash脚本时会用到,三来毕竟是linux原生语言和工具,还是有必要学习的。

想起来读的布莱恩的《Unix 传奇》中对awk以及很多小工具诞生经过的叙述,感觉那个时代(1960-70s)的技术人员真的是好纯粹,最初是因为MIT的计划失败后,At&T实验室1127中心的肯·汤普森等人还想搞操作系统,于是在只有500K磁盘的PDP-7上开始开发Unix系统。肯·汤普森为了读取磁盘上的信息开发了磁盘驱动、在同事的建议下斯图亚特发明了make提升编译效率、道格·麦克尔罗伊受到花园水管的启示发明了管道、本贾尼经常去办公室与它们探讨C++而道格提出了很多建设性意见、然后是各种文字处理工具(因为1127经常要打印文件)、编译器-编译器(如yacc)的发明(因为文字排版或是其它工作常常需要发明新的语言)……一切都是出自对探索技术的热爱和最原始的“想要做得更好”的朴素想法,多想我有一天也能体验这种纯粹的氛围呀

awk和sed都是基于stream的处理工具,这和Unix中管道的机制一脉相承。即:它们总是从输入流一行一行地读取,经过某种处理后一行一行地打印到输出流,且不再回头

区别在于sed一般用来做文本替换,awk一般用来做格式化输出?也不一定,区别慢慢体会

[TOC]

awk

基本语法

直接通过命令行

  • 第一个参数是单引号括起来的pattern和action。注意pattern一定要用斜杠括起来。即:如果当前行的某些数据匹配此pattern(是基于正则的),则对当前行执行action,比如print $0就是打印当前行的第一列;至于怎么将一行分割成列,默认是空格,也可以指定为逗号(如处理csv文件)等,后面会讲。第二个参数是文件名,指定要处理的文件为输入流,也可以同时处理多个文件
1
awk '/somedata/ { print $0 }' filename

通过脚本

  • 提前将命令写在hello.awk中,执行
1
awk -f hello.awk

BEGIN块

awk允许在处理所有输入之前预先执行一段指令,这段指令需要被放在BEGIN块里;对偶的是END块,即处理完所有输入后执行的指令

1
2
3
4
5
$ awk ‘BEGIN { print “Hi Mom,\n\nCamp is fun.\n\nLove,\nSon” }Hi Mom,
Camp is fun.
Love,
Son

print命令

示例文件

/images/image-20220517202404139.png

以下命令可以做到输出以Al开头的行的第一个列的值(即国家名称)

1
awk '/Al/ { print $0 }' countries.txt

当然,也可以不写pattern,这样会匹配所有行

1
awk '{ print $0 }' countries.txt

printf指令

语法和C语言一样,不同的是没有括号

1
$ awk ‘{ printf “Number of cell phones in use in %s: %d\n”, $1, $6 }’ countries.txt

/images/image-20220517203351288.png

可以配合BEGIN块用printf来打印列名

/images/image-20220517203454613.png

指定分割符

正如前文所说,比如在处理内容本身可能带有空格的数据或者处理csv文件时,希望将分割符指定为逗号,只需通过-F选项即可,注意分隔符要紧跟在-F后面。注意不要与-f混淆了,后者是指定程序所在文件

1
2
3
4
5
6
7
$ awk -F, ‘{ print $3 }’ countries.csv
647500
28748
2381740
8833634
468
1246700

也可以通过FS变量指定(变量在下面讲)

1
2
3
# Awk script to print the number of cell phones in use in each country
BEGIN { FS = “,” } # Our data is separated by commas
{ print “Number of cell phones in use in”,$1”:”,$6 }

一个有趣的例子

1
$ echo 'a,,,  b,,,,, c,,   e,, f, g' |awk ‘BEGIN {FS=[,]+[ ]+”} {print $2}

利用了正则表达式匹配

使用变量

和shell脚本中语法一样,比如之前的打印列名的程序可以更简洁,因为列的格式和下面数据格式是一样的,所以可以预先存储在变量中

1
2
BEGIN { colfmt=”%-15s %20s\n; printf colfmt, “Country”, “Cell phones\n}
	  { printf colfmt, $1, $6 }

控制语句

语法和C一样,但是好像因为不能加大括号,只能做一件事

  • if
1
2
3
4
{ if ($3 < 1000)
	printf “%s has only %d people!\n”, $1, $3
else
	printf “%s has a population larger than 1000\n”, $1 }
  • for
1
$ awk ‘BEGIN { for ( myvar = 1; myvar <= 10; myvar++ ) print myvar }
  • while
1
$ awk ‘BEGIN { while (++myvar <= 10 ) print myvar }

运算

完全和C一样,也支持自增自减,+=等

1
2
3
4
5
6
7
$ awk ‘BEGIN {myvar=10; print myvar+myvar}20
$ awk ‘BEGIN {myvar=10; myvar=myvar+1; print myvar}11
$ awk ‘BEGIN {myvar=10; print myvar++; print myvar}10
11

Sed

  • TODO