Chinaunix首页 | 论坛 | 博客
  • 博客访问: 56760
  • 博文数量: 28
  • 博客积分: 84
  • 博客等级: 民兵
  • 技术积分: 162
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-09 17:47
个人简介

try vuuv.github.io or somewhere else.

文章分类

全部博文(28)

文章存档

2013年(1)

2012年(5)

2011年(22)

我的朋友

分类:

2011-10-07 15:00:05

原文地址:awk之getline函数运用 作者:zooyo

    awk可使用shell的重定向符进行重定向输出,如:
  1. $ awk '$1=100{print $1 > "output_file"}' file
上式表示如果第一个域的值等于100,则把它输出到output_file中。也可以用>>来重定向输出,但不清空文件,只做追加操作。
    输入重定向需用到getline函数。getline从标准输入、管道或者当前正在处理的文件之外的其他输入文件获得输入。它负责从输入获得下一行的内容,并给NF,NR和FNR等内建变量赋值。如果得到一条记录,getline函数返回1,如果到达文件的末尾就返回0,如果出现错误,例如打开文件失败,就返回-1。如:
  1. $ awk 'BEGIN{"date"|getline d;split(d,a);print a[2]}'
执行linux的date命令,并通过管道输出给getline,然后再把输出赋值给自定义变量d,并以默认空格为分割符把它拆分开以数字1开始递增方式为下标存入数组a中,并打印数组a下标为2的值。下面我们再看看一些复杂的运用。

1)
文件a
220 34 50 70
553 556 32 21
1 1 14 98 33
文件b
10
8
2
要求文件a的每行数据与文件b的相对应的行的值相减,得到其绝对值。
  1. awk '{getline j<"b";for(i=1;i<=NF;i++){$i>j?$i=$i-j:$i=j-$i}}1' a|column -t
  2. 210 24  40  60
  3. 545 548 24  13
  4. 1   1   12  96  31

[解析]

  getline依次按行读取文件b里的值,然后for循环依次和文件a里的每个字段进行比较,如果比它大就j-$i,要是比它小就$i-j,保证文件相减都是整数,当然更法很多,可以判断是否小于0,小于0就负负为正,也可以替换到负号这个符号等等。总结的说getline函数可以实现2个文件的同步读取而实现一系列的操作。下面是数组的解法:

  1. awk 'ARGIND==1{a[FNR]=$1;next}{for(i=1;i<=NF;i++)$i=$i-a[FNR];$0=gensub(/-/,"","g")}1' b a

 

 

2)

文件a

aaa
bbb
ccc
ddd

文件b

111 xxx
222 xxx
333 xxx
444 xxx

要求文件a里的数据依次替换文件b中的xxx字样。

  1. awk '{getline i<"a"}/xxx/{sub("xxx",i,$2)}1' b
  2. 111 aaa
  3. 222 bbb
  4. 333 ccc
  5. 444 ddd

[解析]

  相信大家已经熟悉getline的用法了吧。呵呵,再看看数组的用法,数组是awk的灵魂,但是有点耗费资源,特别是数百兆上G文件的时候,它挺费劲,大家酌情考虑。

  1. awk 'NR==FNR{a[FNR]=$1;next}/xxx/{++i;$2=a[i]}1' a b

 

 

   在awk运用中,getline可以把当前行的下一行的内容读取给一个变量,也可以在当前行直接把操作转移到下一行操作,有点类似sed里的n功能,下面看看它的这个特性的使用:

 


3)

要求统计每个小时内的访问总和:

2011-07-20 09:57:01
239
2011-07-20 11:03:01
248
2011-07-20 10:29:01
250
2011-07-20 09:56:01
255
2011-07-20 10:45:01
269
2011-07-20 11:27:01
272
2011-07-20 10:28:01
273
2011-07-20 11:32:01
274
2011-07-20 10:44:01
303
2011-07-20 11:36:01
316

 

 

  1. awk -F':' '{i=$1;getline;a[i]+=$1}END{for(j in a)print j,a[j]}' file

[解析]

  因为数据可以看出是很有规律的一行是时间,下一样就是数值,所以把时间赋值给一个变量,然后读取到下一行,把下一行的数值累加给这个时间段为下标的数组a,最后取出结果。

 

  1. awk -F':' '/:/{getline x;a[$1]+=x}END{for(i in a) print i,a[i]}' file

[解析]

  这个getline的意义完全不一样了,当匹配到有冒号的行时执行{action},getline是读取下一行的数据给变量x,然后再根据这个时间的下标把x累加给数组a,最后取出结果。执行的结果都是一样的,大家好好揣摩一下getline的用法区别。



4)


文本:
r9_cz3IwGbu642/1        scaffold_3      9999914
TL_tKUFyGbu642/1        scaffold_2      999991
V4_CUctkGbu642/1        scaffold_30     999990
B6_SIhaqGbu642/1        scaffold_3      9999932
 
先对第三列进行排序,并且计算第三列的后一行减去前一行的值:
V4_CUctkGbu642/1        scaffold_30     999990
TL_tKUFyGbu642/1        scaffold_2      999991     1
r9_cz3IwGbu642/1        scaffold_3      9999914    8999923
B6_SIhaqGbu642/1        scaffold_3      9999932    18
 
 
  1. awk -vcmd="sort -k3,3n  'BEGIN{while(cmd|getline){print $0 FS (i?$3-i:"");i=$3}}

[解析]

    惯用思路想用awk的asorti()函数来排序,新的问题又来了,那如何让后一行减前面一行呢?看来只有先排序后再利用awk逐行执行的个性来操作了。这里我们先把要排序操作的命令记录到一个变量里,再利用getline函数就可以逐行把排序后的文本一一读取到awk中,然后完成相减后输出。这个思路真的很好,没想到直接类似shell的eval $cmd这样的操作手法。 代码不这么风骚点的话,还是老老实实的按照传统模式来吧:


  1. sort -k3,3n file|awk 'NR>1{$0=$0 FS $3-x}{x=$3}1





5)

cat file

4235 3

7 0.051 0.30

0.625 0.675 0.675

8 0.617 0.30

0.419 0.517 0.528

9 0.333 0.30

0.452 0.484 0.493

13 0

14 0

51547 1

2 1.000 0.30


第一行第2个字段为3,表示下面会出现3份这样的数据,为0就没有,为1是会出现一份数据,现在要求把这样的数据复制一份:



4235 3

7 0.051 0.30

0.625 0.675 0.675

8 0.617 0.30

0.419 0.517 0.528

9 0.333 0.30

0.452 0.484 0.493

4235 3

7 0.051 0.30

0.625 0.675 0.675

8 0.617 0.30

0.419 0.517 0.528

9 0.333 0.30

0.452 0.484 0.493

13 0

13 0

14 0

14 0

51547 1

2 1.000 0.30

4.303 4.485 4.614

51547 1

2 1.000 0.30

4.303 4.485 4.614





  1. awk '{if($2==0){print $0"\n"$0}else{c=$2*2;i=$0;for(n=0;n++<c;){getline;i=i"\n"$0};print i"\n"i}}' file


[解析]

判断$2是0的话就直接打印双份,否则通过计算出的行数用for循环通过getline读取下一行累加起来,再统一打印双份。





阅读(1188) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~