免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 6239 | 回复: 15
打印 上一主题 下一主题

[学习共享] CSV的处理,FPAT 的用法 - 只适用于GNU awk 4 以上 [复制链接]

论坛徽章:
15
2015年辞旧岁徽章
日期:2015-03-03 16:54:15双鱼座
日期:2015-01-15 17:29:44午马
日期:2015-01-06 17:06:51子鼠
日期:2014-11-24 10:11:13寅虎
日期:2014-08-18 07:10:55酉鸡
日期:2014-04-02 12:24:51双子座
日期:2014-04-02 12:19:44天秤座
日期:2014-03-17 11:43:36亥猪
日期:2014-03-13 08:13:51未羊
日期:2014-03-11 12:42:03白羊座
日期:2013-11-20 10:15:18CU大牛徽章
日期:2013-04-17 11:48:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-02-19 14:08 |只看该作者 |倒序浏览
本帖最后由 rdcwayx 于 2014-03-24 13:09 编辑

谈一下如果方便的处理csv格式的文件,只适用于GNU awk 4 以上版本

假设存在以下的csv文件(逗号分隔值),内容为如下格式:
  1. Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA
复制代码
注意,其中的字段("1234 A Pretty Street, NE")中包含了一个“,”,传统的FS=","来分隔域就会出错,地址会被拆分成两部分:
  1. 1234 A Pretty Street  和  NE
复制代码
这不是我们想要的结果。

针对这样的场景,可以使用内置变量FPAT来解决问题。FPAT的值是一个正则表达式,该正则表达式描述了每一个域的内容。
  1. FPAT = "([^,]+)|(\"[^\"]+\")"
复制代码
上面的FPAT表示,每个域或者是不包含","的字符串,或者是由一对双引号括起来的字符串。

因此,我们可以这样来解决:

复制代码
  1. BEGIN {
  2.          FPAT = "([^,]+)|(\"[^\"]+\")"
  3.      }
  4.      {
  5.          print "NF = ", NF
  6.          for (i = 1; i <= NF; i++) {
  7.              printf("$%d = <%s>\n", i, $i)
  8.          }
  9.      }
复制代码
这个是运行结果
  1. $ gawk -f simple-csv.awk addresses.csv
  2. NF =  7
  3. $1 = <Robbins>
  4. $2 = <Arnold>
  5. $3 = <"1234 A Pretty Street, NE">
  6. $4 = <MyTown>
  7. $5 = <MyState>
  8. $6 = <12345-6789>
  9. $7 = <USA>
复制代码
第三个字段(含逗号)作为一个域了。

再进一步,如果不希望有双引号,
  1.      if (substr($i, 1, 1) == "\"") {
  2.          len = length($i)
  3.          $i = substr($i, 2, len - 2)    # Get text within the two quotes
  4.      }
复制代码
参考: https://www.gnu.org/software/gaw ... ing-By-Content.html

注意:这个FPAT设置有个限制,不能处理双引号里含换行符的情况。比如这个例子: http://bbs.chinaunix.net/thread-4132643-1-1.html
  1. NOTE: Some programs export CSV data that contains embedded newlines between the double quotes. gawk provides no way to deal with this. Since there is no formal specification for CSV data, there isn't much more to be done; the FPAT mechanism provides an elegant solution for the majority of cases, and the gawk maintainer is satisfied with that.
复制代码

评分

参与人数 1可用积分 +10 收起 理由
Shell_HAT + 10 赞一个!

查看全部评分

论坛徽章:
33
ChinaUnix元老
日期:2015-02-02 08:55:39CU十四周年纪念徽章
日期:2019-08-20 08:30:3720周年集字徽章-周	
日期:2020-10-28 14:13:3020周年集字徽章-20	
日期:2020-10-28 14:04:3019周年集字徽章-CU
日期:2019-09-08 23:26:2519周年集字徽章-19
日期:2019-08-27 13:31:262016科比退役纪念章
日期:2022-04-24 14:33:24
2 [报告]
发表于 2014-02-19 14:32 |只看该作者
学习了

论坛徽章:
32
处女座
日期:2013-11-20 23:41:20双子座
日期:2014-06-11 17:20:43戌狗
日期:2014-06-16 11:05:00处女座
日期:2014-07-22 17:30:47狮子座
日期:2014-07-28 15:38:17金牛座
日期:2014-08-05 16:34:01亥猪
日期:2014-08-18 13:34:25白羊座
日期:2014-09-02 15:03:55金牛座
日期:2014-11-10 10:23:58处女座
日期:2014-12-02 09:17:52程序设计版块每日发帖之星
日期:2015-06-16 22:20:002015亚冠之塔什干火车头
日期:2015-06-20 23:28:22
3 [报告]
发表于 2014-02-19 14:41 |只看该作者
学习了~
  1. $ echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' | awk -vFPAT='([^,]+)|("[^"]+")' '{for(i=0;i++<NF;)print $i}'
  2. Robbins
  3. Arnold
  4. "1234 A Pretty Street, NE"
  5. MyTown
  6. MyState
  7. 12345-6789
  8. USA
复制代码

论坛徽章:
145
技术图书徽章
日期:2013-10-01 15:32:13戌狗
日期:2013-10-25 13:31:35金牛座
日期:2013-11-04 16:22:07子鼠
日期:2013-11-18 18:48:57白羊座
日期:2013-11-29 10:09:11狮子座
日期:2013-12-12 09:57:42白羊座
日期:2013-12-24 16:24:46辰龙
日期:2014-01-08 15:26:12技术图书徽章
日期:2014-01-17 13:24:40巳蛇
日期:2014-02-18 14:32:59未羊
日期:2014-02-20 14:12:13白羊座
日期:2014-02-26 12:06:59
4 [报告]
发表于 2014-02-19 14:52 |只看该作者
回复 1# rdcwayx

try this way if you didn't have awk 4.0

$ awk -f csv.awk FILE
NF =  8
modify for csv
NF =  7
$1 = <Robbins>
$2 = <Arnold>
$3 = <"1234 A Pretty Street, NE">
$4 = <MyTown>
$5 = <MyState>
$6 = <12345-6789>
$7 = <USA>

$ cat csv.awk
BEGIN{
  FS=","
}
{
  print "NF = ", NF
  c = 0
  for (n = 1; n <= NF; n++) {
    if(q == 1){
      if($n~/"$/){
      #if(sub(/"$/,"",$n)){   # for removed the "
        q = 0
      }
      $c = $c FS $n
      continue
    }
    if($n~/^"/){
    #if(sub(/^"/,"",$n)){   # for removed the "
      q = 1
      $(++c) = $n
      continue
    }
    $(++c) = $n
  }
  NF = c
  print "modify for csv"   
  print "NF = ", NF
  for (n = 1; n <= NF; n++) {
    printf("$%d = <%s>\n", n, $n)
  }
}

   

论坛徽章:
5
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:50:282015年亚洲杯之朝鲜
日期:2015-03-13 22:47:33IT运维版块每日发帖之星
日期:2016-01-09 06:20:00IT运维版块每周发帖之星
日期:2016-03-07 16:27:44
5 [报告]
发表于 2014-02-19 14:54 |只看该作者
新东西啊,学习。

论坛徽章:
3
摩羯座
日期:2014-03-05 14:58:52巨蟹座
日期:2014-04-03 15:14:32摩羯座
日期:2014-04-24 12:50:34
6 [报告]
发表于 2014-02-19 14:57 |只看该作者
学习了!

比grep -Po安全:
  1. $ grep -Po "([^,]+)|(\"[^\"]+\")" urfile
  2. Robbins
  3. Arnold
  4. "1234 A Pretty Street
  5. NE"
  6. MyTown
  7. MyState
  8. 12345-6789
  9. USA
  10. $ grep -Po "(\"[^\"]+\")|([^,]+)" urfile
  11. Robbins
  12. Arnold
  13. "1234 A Pretty Street, NE"
  14. MyTown
  15. MyState
  16. 12345-6789
  17. USA
复制代码

论坛徽章:
29
程序设计版块每日发帖之星
日期:2016-02-29 06:20:0015-16赛季CBA联赛之天津
日期:2016-08-10 10:33:1115-16赛季CBA联赛之深圳
日期:2016-08-17 15:07:2015-16赛季CBA联赛之佛山
日期:2016-11-07 11:33:5015-16赛季CBA联赛之广夏
日期:2016-11-15 09:13:31CU十四周年纪念徽章
日期:2016-11-24 14:12:25极客徽章
日期:2016-12-07 14:03:4015-16赛季CBA联赛之深圳
日期:2016-12-07 17:15:2715-16赛季CBA联赛之北京
日期:2016-12-22 09:30:0115-16赛季CBA联赛之深圳
日期:2016-12-22 10:49:2115-16赛季CBA联赛之山西
日期:2017-02-10 09:05:3215-16赛季CBA联赛之同曦
日期:2017-02-27 14:19:08
7 [报告]
发表于 2014-02-19 23:08 |只看该作者
楼主的新知识要跟进,6楼的grep差异也值得注意, 学习啊

论坛徽章:
15
2015年辞旧岁徽章
日期:2015-03-03 16:54:15双鱼座
日期:2015-01-15 17:29:44午马
日期:2015-01-06 17:06:51子鼠
日期:2014-11-24 10:11:13寅虎
日期:2014-08-18 07:10:55酉鸡
日期:2014-04-02 12:24:51双子座
日期:2014-04-02 12:19:44天秤座
日期:2014-03-17 11:43:36亥猪
日期:2014-03-13 08:13:51未羊
日期:2014-03-11 12:42:03白羊座
日期:2013-11-20 10:15:18CU大牛徽章
日期:2013-04-17 11:48:45
8 [报告]
发表于 2014-02-20 09:07 |只看该作者
楼上的两个方法也不错啊。

论坛徽章:
0
9 [报告]
发表于 2014-02-20 09:41 |只看该作者
本帖最后由 jiejie455 于 2014-02-20 10:11 编辑

没有GAWK的时候:
  1. awk 'BEGIN{FS=",";}{for(i=1;i<=NF;i++){str=$i;if(sub(/^"/,"",$i)==1 && sub(/"$/,"",$i)==0){i=i+1;str=str","$i};print str}}' data
复制代码
@yestreenstars REVISED:
  1. awk 'BEGIN{FS=","}{for(i=1;i<=NF;i++){flag=1;str=$i;if(sub(/^"/,"",$i)==1 && sub(/"$/," ",$i)==0){while(flag==1){i=i+1;str=str","$i;if(sub(/"$/," ",$i)==1){flag=0}}};print str}}' data
复制代码

论坛徽章:
32
处女座
日期:2013-11-20 23:41:20双子座
日期:2014-06-11 17:20:43戌狗
日期:2014-06-16 11:05:00处女座
日期:2014-07-22 17:30:47狮子座
日期:2014-07-28 15:38:17金牛座
日期:2014-08-05 16:34:01亥猪
日期:2014-08-18 13:34:25白羊座
日期:2014-09-02 15:03:55金牛座
日期:2014-11-10 10:23:58处女座
日期:2014-12-02 09:17:52程序设计版块每日发帖之星
日期:2015-06-16 22:20:002015亚冠之塔什干火车头
日期:2015-06-20 23:28:22
10 [报告]
发表于 2014-02-20 09:48 |只看该作者
回复 9# jiejie455

有局限性:如果双引号内含有1个以上的逗号就不行了~比如:
  1. Robbins,Arnold,"1234, A Pretty Street, NE",MyTown,MyState,12345-6789,USA
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP