那些年我用awk时踩过的坑——awk使用注意事项
由于项目经历原因,经常使用awk处理一些文本数据。甚至,我特意下载了一个windows上的awk:gawk.exe,这样在windows上也能享受awk处理数据的方便性,。
俗话说,“常在河边走,哪能不湿鞋”,使用awk过程中碰上过不少坑,这里稍总结一下,希望对大家有帮助。
1 FS问题
看看这两个awk脚本:
cat demo_1.txt demo_2.txt
||||
|@||@||@||@|
awk -F '|' '{print $2}' demo_1.txt; # 脚本1
awk -F '|@|' '{print $2}' demo_2.txt; # 脚本2
脚本原目的是达到的目的是分别按'|'和分隔'|@|',输出demo.txt第二列。但实际上,第一个脚本这样写没错,但第二个脚本却是错的。
为什么呢?
因为竖线在正则表达式中是一个特殊字符,表示匹配竖线左右的字符组之一。如果想使用竖线本身,需要对用转义符。
但为什么第一个命令也一样使用了竖线却没有问题呢?
这就涉及到awk在一个规定:
如果FS设置了不止一个字符作为字段分隔符,将作为一个正则表达式来解释,否则直接按该字符做为分隔符对每行进行分割。
所以第一个命令使用了竖线做分隔符没问题,第二个命令就出错了。
2 正则表达式与反斜杠号问题
继续上面的问题讨论,如果demo.txt是按"|@|"做为分隔符的,要输出demo.txt第二列,正确的答案应该是怎么写呢?
答案是:
awk -F '\\|@\\|' '{print $2}' demo.txt;
注意这里,FS的值是'\\|@\\|',而不是简单的'\|@\|'(这样写会报错,提示:awk: 警告: 转义序列“\|”被当作单纯的“|”)。为啥要这样写呢?
先来看一个试验:
echo|awk -F '|@|' '{print FS}' # 脚本1
echo|awk -F '\|@\|' '{print FS}' # 脚本2
echo|awk -F '\\|@\\|' '{print FS}' # 脚本3
可以看到第一和第二个脚本,FS值是一样的。原因是awk先要解析用户输入的字符串,并将解析结果赋值给FS,然后再调用split类函数,把FS当成函数参数传进去。
而split需要再对FS进行一次解析,编译成正则表达式。awk解析字符串给FS变量赋值时会把'\|'认为是'|',从而导致传进split函数时,分隔符已。
因此,如果想让awk正确分割记录,需要使FS='\\|@\\|',这时awk会把\\解析成转义字符'\',这样竖线就能被当普通字符处理国。
3 关联数组访问问题
曾经碰上过这样一个场景:文件a.txt包含少量用户余额(userid|amt),约100行记录,文件b.txt包含了所有用户的余额(userid|amt),约有100万行记录。
现在要求关连a.txt和b.txt(使用userid),找出在a.txt与b.txt都存在的userid,并输出其中b.amt大于a.amt的记录。
当时我先写了以下脚本:
awk -F '|' 'BEGIN{ while(getline < "a.txt") { v_user_map[$1] = $2; } }
{
v_amt_a = v_user_map[$];
if ((v_amt_a != "") && v_amt_a < $) print $;
}
看起来逻辑似乎没有问题,于是开始跑。但是跑起来发现效率远比自己想象的低,而且发现程序运行过程消耗的内存越来越多。
这明显是有问题的,理论上应该是BEGIN那段语句会消耗一些内存,之后应该就不需要再消耗才对。
由于写过c++代码,里面也有类似关联数组的数据结构,我很快猜测并实验证明原因:v_amt_a = v_user_map[$1]; 这一句。
虽然这里没有给v_user_map[$1]赋值,但是awk会默认赋值为空,导致v_user_map数组元素越来越多,占用内存空间越来越大,查找效率越来越低。
知道问题就好解决了,查了一下awk帮助手册,发现可以这样写:
awk -F '|' 'BEGIN{ while(getline < "a.txt") { v_user_map[$1] = $2; } }
{
if ($ in v_user_map) { if (v_user_map[$] < $) print $; }
}
使用in操作符来判断元素是否在关联数组里面,这样就不会有默认赋值。
4 内存限制问题
如果awk是32位程序(可以使用file命令判断),那么上面的脚本1,很可能跑着跑着就core了。因为默认情况下,32位的awk最多只能消耗256M内存。
如果申请内存超过这个数就会发生异常退出。
解决方法是使用64位程序,或者修改环境变量“export LDR_CNTRL=MAXDATA=0x80000000”。(AIX4.3以上有效)
5 getline返回值问题
注意楼上的getline用法,while(getline < "a.txt")循环读取文件直到结束。这样写其实是不太规范的,有隐患。
曾经我以为getline读到文件尾会把$0置空,后来实践发现实际不是这样的。geline在碰上文件尾时会返回0,但$0还是保持最后一行的记录不变。于是就改成这种写法。
不过这种写法,有时也会碰上问题,原因:getline返回值有三种情况:1 正常读取到一条记录 0 达到文件尾 -1 文件不存在或其它错误。
如果a.txt不存在,getline会返回-1,导致死循环。我以前曾经碰上过因为这个原因导致程序挂死,所以特别提出来让大家注意。
建议大家使用函数前最好先看看帮助文档里面关于函数描述。
6 管道问题
先来看这个脚本:
ls -1rt
demo.txt
list.txt
echo -e "\n\n" | awk '{ while("ls -1rt" | getline) { print NR " : " $0 > "list.txt";}}'
猜猜看:脚本运行完后list.txt里面的内容是什么?
答案:
cat list.txt
: demo.txt
: list.txt
相信有不少朋友会觉得诧异:
有些人会认为list.txt里面应该只有一行数据,就是ls -1rt命令输出内容的最后一行。
有些人会认为应该有6条数据才对,因为ls -1rt执行了三次。
有这种想法的人,多半是不知道awk一个规定: 默认情况下同一个文件或者管道只打开一次,如果需要重复打开,需要先close。
上面的脚本由于没有显式close文件和管道,list.txt和ls -1rt都只打开/执行了一次,所以输出结果如上。
再猜猜看:下面这个脚本运行完后list.txt里面的内容是什么?
echo -e "\n\n" | awk '{ while("ls -1rt" | getline) { print NR " : " $0 > "list.txt";} close("list.txt"); close("ls -1rt");}'
7 输出单引号问题
大家知道,awk脚本一般是用单引号括起来的,形如:awk '{ print "do something"; }' 。
因此,在awk中要使用单引号是比较麻烦的事情。网上找awk输出单引号一般可以找到以下方法:
echo | awk '{ print "'\''"; }'
很多人因此就误会了,以为awk脚本由于使用了单引号做为脚本开始结束标志,所以在awk脚本里面是不能直接使用单引号的。
其实这是误会了,看下面的脚本你就知道。
cat demo.awk
{ print "'"; }
echo | awk -f demo.awk
'
可见,awk脚本是可以直接使用单引号的,也不需要使用单引号把脚本括起来。 之所以在命令行需要用这么别扭的写法,是因为shell的关系:使用单引号括起来的内容,不会被shell当成特殊字符处理。
因为awk脚本里面经常需要$n来获取第几个字段的内容,而$在shell里面是有特殊意义的,代表变量开始。 如果不用单引号括起来,就会出问题。
'{ print "'\''"; }' 这段可以这样理解:脚本分三段
、 '{ print "' ; 、 \'; 、 '"; }';
每段被shell解析后是这样的
、 { print " ; 、 '; 、 "; } ;
三段合起来就是传给awk的脚本内容:{ print "'"; }。理解了这个之后,在windows使用awk碰上以下问题,你就知道怎么解决了:
C:\Users\hch>awk '{print "";}'
awk: '{print
awk: ^ invalid char ''' in expression
8 自动隐式转换问题
在c语言里面,我们习惯了整数相除,结果还是整数。所以5/2结果是2,不是2.5。
然而在awk里面,由于没有明确指定变量类型,所以在变量计算过程经常会发现隐式转换,整数相除结果可能是小数。
举例:
echo | awk '{v_result = 5 / 2; print v_result}'
2.5
如果我们想要实现c语言的整数相除效果,要怎么办呢? 可以使用int函数,如下:
echo | awk '{v_result = int(5 / 2); print v_result}'
9 中文竖线问题
实际工作中,经常碰上文件中每行记录里面用竖线'|'做为分隔符的,如"a|b|c|d"。如果文件里面没有中文,这样做是没问题的。
但如果有中文,特别是gbk编码在中文时,这样做就容易出问题了。
gbk编码中,中文由两个字节组成,第一个字节取值范围是[128, 256),第二个字节取值范围是[0, 256)。
如果第二个字节值正好是'124',也就是'|'字符的asscii码,awk处理时就会误以为这个字节是分隔符,从而导致分割字符串时出现错乱。
那有哪些中文是这样的呢? 可以用以下脚本输出gbk编码中包含竖线的特殊中文:(其它编码类似)
echo|awk '{for(i = 128; i < 256; i++) { printf("%c| ", i); } }' #终端编码要是GBK
€| 亅 倈 億 剕 厊 唡 噟 坾 墊 妡 媩 寍 峾 巪 弢 恷 憒 抾 搢 攟 晐 東 梶 榺 檤 殀 泑 渱 潀 瀨 焲 爘 ▅ ﹟ 獆 珅 瑋 瓅 畖 瘄 皘 眧 瞸 硘 磡 祙 秥 穦 竱 箌 簗 粅 紎 絴 緗 縷 纜 羭 聕 脇 膢 舼 苵 莬 葇 蓔 蕓 藎 蘾 蛗 蝲 蟶 衸 褆 襹 觸 詜 諀 謡 讄 貄 質 趞 踻 軀 輡 迀 遼 鄚 醸 鈢 銃 鋦 鍇 鎩 鐋 鑭 閨 陓 雦 靯 韡 顋 飢 饇 駖 騶 髚 魘 鮸 鰘 鱸 鴟 鵿 鷟 鹼 鼃 齶
碰上这种情况暂时我没有发现太好的处理方法,建议使用比较长的分隔符,减少碰上问题的概率,如'|@|'。
如果分隔符不可变,那可以考虑使用iconv转换编码,处理完后再转换回来。
10 函数名与变量名冲突
awk内置了很多函数,如果不小心把变量名字取得跟这些函数名字一样,程序就会报错。提示很不清楚,就只是说错了,不说原因,特别坑。
例如以下这个报错:
awk '{ if (NR == FNR) { sub[$1] = $2; } else { print sub[$1]; } }' subsid_amt.txt subsid.txt
awk: { if (NR == FNR) { sub[$] = $; } else { print sub[$]; } }
awk: ^ syntax error
由于这个脚本是晚上加班到深夜时写的,当时头脑不清醒,看到报错蒙了好久:怎么看语法都是对的,但是运行却总是提示语法错了。
所以现在我写比较复杂的awk脚本,变量名都习惯前面加上v_后缀,这样可以减少名字冲突的概率。
暂时就总结了这些。如果大家也碰上过使用awk的问题,不妨一起发出来讨论一下吧:)
那些年我用awk时踩过的坑——awk使用注意事项的更多相关文章
- [问题解决]RedHat7更换CentOS7的yum源时踩过的坑
更换yum源的流程 查看当前yum程序 $ rpm -qa|grep yum 这里推荐将其结果截屏或拷贝出来,以免后面报错修复. 删除原有yum源 $ rpm -aq | grep yum|xargs ...
- 使用Ajax中get请求发送Token时踩的那些坑
在使用惯了各种牛X的插件以后,在使用原生组件写一些小东西的时候总是有踩不完的坑! 今天就来说一说我使用原生ajax请求时踩得坑: 下面是我的代码: var xmlhttp; if (window.XM ...
- 记录初学者学习Hive时踩过的坑
1. 缺少MySQL驱动包 1.1 问题描述 Caused by: org.datanucleus.store.rdbms.connectionpool.DatastoreDriverNotFound ...
- 曲演杂坛--使用CTE时踩的小坑:No Join Predicate
在一次系统优化中,意外发现一个比较“坑”的SQL,拿出来供大家分享. 生成演示数据: --====================================== --检查测试表是否存在 IF(O ...
- 配置tomcat时踩过的坑
conf/server.xml文件有2处地方容易踩坑: 1.<HOst>标签中的appBase名字,要看清,有些修改的版本,会把这个名字改成deploy,而eclipse默认部署的是web ...
- 前端开发工具Brackets介绍,安装及安装Emme插件时踩过的坑
对于前端开发的园友来说有可能IDE工具有很多,层次不穷,还有每个人的喜好及习惯也不一样,因为我是一名后端开发的.Net程序员,但是大家都知道,现在都提倡什么全栈工程师,所以也得会点前端开发,所以我对于 ...
- 记录在vue中使用jsx时踩过的坑
使用方法及细节就不一一说了. 1.给input或者textarea绑定value时,出现失效的问题.解决方法:https://github.com/vuejs/babel-plugin-transfo ...
- python与C,在写程序时踩过的坑!
1. python与C有很多相似之处, 其一就是指针的大量应用, 因此在使用临时变量保存数据, 并将临时变量传递给其他变量时需要创建内存; 例如,在C中, char *temp 每次获取到不同的字 ...
- [转] 那些在使用webpack时踩过的坑
用webpack的过程,就是一个不断入坑和出坑的过程.回望来时路,一路都是坑啊!现把曾经趟过的那些坑整理出来,各位看官有福了~ 文章末尾有我用到的依赖的版本信息,若你发现某个问题与我在本文中的描述不一 ...
随机推荐
- swift把汉字转换为拼音,并且截取首字母做索引用
var transformContents = CFStringCreateMutableCopy(nil, 0, "咋啊的看到回复阿斯顿发货发哦iasdifas")CFStrin ...
- iOS 事件处理机制与图像渲染过程(转)
iOS 事件处理机制与图像渲染过程 iOS RunLoop都干了什么 iOS 为什么必须在主线程中操作UI 事件响应 CALayer CADisplayLink 和 NSTimer iOS 渲染过程 ...
- 效果类似于label从下往上滑(采用uiTableView实现)
首先附上效果图 进行描述一下:效果就是类似于是一个竖直方向的滚动视图 并且方向是从下往上 并且能够一直这样循环下去. 代码“ // // ViewController.m // demo滚动视图上下 ...
- winform降低功耗总结
这里整理了一些网上关于Winform如何降低系统内存占用的资料,供参考: 1.使用性能测试工具dotTrace 3.0,它能够计算出你程序中那些代码占用内存较多2.强制垃圾回收3.多dispose,c ...
- Codeforces 193D Two Segments 解题报告
先是在蓝桥杯的网站上看到一道题: 给出1~n的一个排列,求出区间内所有数是连续自然数的区间的个数.n<=50000. 由于数据较弱,即使用O(N^2)的算法也能拿到满分. 于是在CF上发现了这一 ...
- Cocos2dx开发(2)——Win8.1下Cocod2dx 3.2环境搭建
正式开始搭建cocos2dx环境,回到熟悉的VS 1.Python安装配置 这一步很简单,下载Python2.7.3,笔者直接用软件助手直接下载安装,最后配置环境变量 如下成功 2.cocos2dx ...
- 微信开放平台获取component_verify_ticket
官方文档说明: 在公众号第三方平台创建审核通过后,微信服务器会向其“授权事件接收URL”每隔10分钟定时推送component_verify_ticket.第三方平台方在收到ticket推送后也需进行 ...
- C程序设计语言练习题1-16
练习1-16 修改打印最长文本行的程序的主程序main,使之可以打印任意长度的输入行的长度,并尽可能多地打印文本. 代码如下: #include <stdio.h> // 包含标准库的信息 ...
- 得到指定进程PID
//#include "targetver.h" #include "stdio.h" #include <windows.h> #include ...
- C# asp:Repeater DataSource List<T>
1. asp:Repeater 数据源为List<T> 2.页面显示 3.行绑定取值