标题:[实践OK]AWK的NR和FNR涉及高级技巧,用一行awk命令处理错开又有对应关系的两列,比用Excel更优雅,比paste更精细且相当优雅。 出处:向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除 时间:Thu, 24 Mar 2016 19:03:56 +0000 作者:jackxiang 地址:http://jackxiang.com/post/8593/ 内容: 背景:我们特别是在想对两个都各带一列且没有行对齐且行数不一致的关联性的文件做关联成一行的时候,会想用自己熟悉的语言去先后打开两个文件(可能高级点的语言可以一次读取到内存,以PHP这种擅长处理字符串的语言为例,且文件要小,当然你可以对PHP占用内存作调整配置。),对文件里面的所有列全部读取到数组,再用循环对数组每行进行分割提取出关联行,再读取另外一个文件一样分离出相同的部分形成数组键,值就是一行,对第一个文件里面的关联列一个一个去第二个文件里面的数组里循环查找到查到为止,找到后,对以找到的这行的key定了,当然就对应上了,这个看起来简单,得6到7行,往往伴随着那个一行里面提取部分,也就是说需要arr=explode(sep,line),取arr某些键值和第一个文件(也可能需要类似explode操作)有关联的显示出来,你还会说,这个不难,有Excel可以实现,先对这两个文件所关联列进行排序,然后,复制粘贴在一块就OK了,感觉很简单也挺棒的,但是,假如两行的数据里面有重复行,两个文件不一样的行数,这个再像linux下的paste俩文件一样行去搞可能就会有问题了,是不是有点难搞,还得去重复啥的,其灵活性可能还是欠佳,往往需求上对重复部分只留下一个就OK的处理方式居多,Excel想做到这样引入了去重复后让两行一样多的操作,SO,有没有简单可行,优雅简洁的处理方式呢?有,那就是上面描述的可以用awk一行搞定,听说由三个数学家的名字首字母发明出来的,它优雅的使用两个类似宏的东西NR,FNR两个变量实现了上面一堆功能,对行和列处理应该相当有水平,在此,我向他们致敬。 ————————————————————————————————————————————————————————————————————————— 书上说: NR,表示awk开始执行程序后所读取的数据行数. FNR,与NR功用类似,不同的是awk每打开一个新文件,FNR便从0重新累计. 下面看两个例子: 1,对于单个文件NR 和FNR 的 输出结果一样的 : # awk '{print NR,$0}' file1 1 a b c d 2 a b d c 3 a c b d #awk '{print FNR,$0}' file1 1 a b c d 2 a b d c 3 a c b d 2,但是对于多个文件 : # awk '{print NR,$0}' file1 file2 1 a b c d 2 a b d c 3 a c b d 4 aa bb cc dd 5 aa bb dd cc 6 aa cc bb dd # awk '{print FNR,$0}' file1 file2 1 a b c d 2 a b d c 3 a c b d 1 aa bb cc dd 2 aa bb dd cc 3 aa cc bb dd 在看一个例子关于NR和FNR的典型应用: 现在有两个文件格式如下: #cat account 张三|000001 李四|000002 #cat cdr 000001|10 000001|20 000002|30 000002|15 想要得到的结果是将用户名,帐号和金额在同一行打印出来,如下: 张三|000001|10 张三|000001|20 李四|000002|30 李四|000002|15 执行如下代码 #awk -F \| 'NR==FNR{a[$2]=$0;next}{print a[$1]"|"$2}' account cdr 注释: 由NR=FNR为真时,判断当前读入的是第一个文件account,然后使用{a[$2]=$0;next}循环将account文件的每行记录都存入数组a,并使用$2第2个字段作为下标引用. 解释,在读取文件1时候,形成如下数组结构: a[000001]=张三|000001 a[000002]=李四|000002 ………… 由NR=FNR为假时,判断当前读入了第二个文件cdr,然后跳过{a[$2]=$0;next},对第二个文件cdr的每一行都无条件执行{print a[$1]"|"$2},此时变量$1为第二个文件的第一个字段,与读入第一个文件时,采用第一个文件第二个字段$2为数组下标相同.因此可以在此使用a[$1]引用数组。 解释,按顺序读取到第二个文件时候,文件二第一行输出数组和第二列,如下: a[000001] | 10 => 张三|000001 | 10 awk -F \| 'NR==FNR{a[$2]=$0;next}{print a[$1]"|"$2} 第一个文件的第二列和第二个文件的第一列关联,a数组存的是第一个文件的按第二列为键值每行。 总结,NR,FNR结合next循环,在数组的key存放关键关联列信息,这个逻辑的核心就在于此,再通过这个awk列$N(N为第几列)优势,只要有关联列,就能这样去匹配出来的行可以任意列输出,相当灵活,优雅,简洁。 =====================自己在日常中的实践如下AddTime:2016-03-25=========================== 两个文件里都有UUID的编号及对应的线上机器下载的UUID文件名及后缀,想变成对应的用户上传的中文名加后缀: (中文名里有各种符号像中文的括号、竖线、空格啥的都得去掉,否则批量肯定会没法成功重命名,需求相当可恶) windows操作系统虽然好用,但是并不适合研发人员,它的命令行(暂且只说dos, 虽然可以用vb, vbscript, 但是本人不太熟悉,写法上也不太适合做编程)实在是太弱了,想想linux下的shell,用起来还是挺方便的! 方法一: bianhua_move_comand.txt //得把那个uuid列先单出来,当然,也可用split进行分割直接取数组1,先讲一次性解决,再讲两步来处理。 cee7b22e-f502-4fed-85a4-1db2c8c42468.mp4 1aabd2ac-2133-43d6-921d-b5bd2a34182e.mp4 ./bianhua_title_uuid.txt 【新春征集】重庆两江新区《龙兴》 cee7b22e-f502-4fed-85a4-1db2c8c42468 【新春征集】重庆两江新区《伟业》 1aabd2ac-2133-43d6-921d-b5bd2a34182e awk能够指定分隔符既可以为制表符,又可以为点,那么处理将会变得简单。可以使用正则表达式来指定多个分隔符,格式为 -F[\\t.], 直接指定两个分割符号,分别是\t,和.点,注意斜杠需要转义,awk中-F可指定两种或多种分割符号用[]括起来,awk语句及执行如下所示: awk -F[\\t.] 'NR==FNR{a[$1]=$0;next}{split(a[$2],array,".");print "mv /path/"a[$2]" /path/newfolder/"$1"."array[2]}' bianhua_move_comand.txt bianhua_title_uuid.txt 结果达到了mv移动的目的: ...... mv /path/cee7b22e-f502-4fed-85a4-1db2c8c42468.mp4 /path/newfolder/【新春征集】重庆两江新区《龙兴》.mp4 mv /path/1aabd2ac-2133-43d6-921d-b5bd2a34182e.mp4 /path/newfolder/【新春征集】重庆两江新区《伟业》.mp4 方法二: 当然,也可分两次来做,把关联的列都单独出来后,再用awk去做这个事情,总之,都需要写split分割,如下步骤: awk -F. '{print $1"\t"$0"\t"$2}' bianhua_move_cond.txmat > bianhua_move_comand2.txt bianhua_move_comand2.txt 经上面的awk处理后其 ./bianhua_move_comand2.txt 结构如下所示: 1aabd2ac-2133-43d6-921d-b5bd2a34182e 1aabd2ac-2133-43d6-921d-b5bd2a34182e.mp4 .mp4 1aabd2ac-2133-43d6-921d-b5bd2a34182e 1aabd2ac-2133-43d6-921d-b5bd2a34182e.mp4 .mp4 把以UUID的文件移到新的目录,并以中文对应名进行赋值,如下: awk -F\\t 'NR==FNR{a[$1]=$0;next}NR>FNR{if($2 in a) split(a[$2],array,"\\t");print "mv /path/"array[2] " /path/newfolder/"$1array[3]}' bianhua_move_comand2.txt bianhua_title_uuid.txt 其实,去掉NR>FNR也一样可以运行,如下: awk -F\\t 'NR==FNR{a[$1]=$0;next}{if($2 in a) split(a[$2],array,"\\t");print "mv /path/"array[2] " /path/newfolder/"$1array[3]}' bianhua_move_comand2.txt bianhua_title_uuid.txt 最后,还可以去掉in的数组判断,更加简化: awk -F\\t 'NR==FNR{a[$1]=$0;next}{split(a[$2],array,"\\t");print "mv /path/"array[2] " /path/newfolder/"$1array[3]}' bianhua_move_comand2.txt bianhua_title_uuid.txt mv /path/cee7b22e-f502-4fed-85a4-1db2c8c42468.mp4 /path/newfolder/【新春征集】重庆两江新区《龙兴》.mp4 mv /path/1aabd2ac-2133-43d6-921d-b5bd2a34182e.mp4 /path/newfolder/【新春征集】重庆两江新区《伟业》.mp4 附录: split(str,array,sep) 使用分隔符sep把字符串分解成数组array if($2 in a) //假如这个$2变量在数组里面 -F \\t \| //以斜杠转义下斜杠,制表符和竖线,-F是awk指定的分割符。 假如用PHP实现的大致的流程,和上面一行解决问题起到一个比对作用: rename.php $value){ foreach($fileOneArr as $key2=>$value2){ $uuid = substr($value, -36);//GET UUID if(strpos($value2,$uuid) != FALSE){ $chines= explode("\t",$value); $prefix = explode(".",$value2); $strOut = "mv /upload/allDownloadTxtFolder2/bianhua/".$value2." "."/upload/allDownloadTxtFolder2/bianhua/".$chines.$prefix[1]; echo $strOut; break; } } } 实践的最初素材来自:http://www.linuxidc.com/wap.aspx?nid=61174 ,及相关人的讲解,Thanks... Generated by Jackxiang's Bo-blog 2.1.1 Release