使用awk解析csv并忽略字段内的逗号

时间:2010-11-17 14:35:04

标签: csv awk

我有一个csv文件,其中每行定义给定建筑物中的房间。与房间一起,每排都有一个场地。我要提取的是所有建筑物的所有楼层。

我的文件看起来像这样......

"u_floor","u_room","name"
0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL, JOHN W    "
0,3,"BRICKER HALL, JOHN W    "
0,5,"BRICKER HALL, JOHN W    "
0,6,"BRICKER HALL, JOHN W    "
0,7,"BRICKER HALL, JOHN W    "
0,8,"BRICKER HALL, JOHN W    "
0,9,"BRICKER HALL, JOHN W    "
0,19,"BRICKER HALL, JOHN W    "
0,20,"BRICKER HALL, JOHN W    "
0,21,"BRICKER HALL, JOHN W    "
0,25,"BRICKER HALL, JOHN W    "
0,27,"BRICKER HALL, JOHN W    "
0,29,"BRICKER HALL, JOHN W    "
0,35,"BRICKER HALL, JOHN W    "
0,45,"BRICKER HALL, JOHN W    "
0,59,"BRICKER HALL, JOHN W    "
0,60,"BRICKER HALL, JOHN W    "
0,61,"BRICKER HALL, JOHN W    "
0,63,"BRICKER HALL, JOHN W    "
0,"0006M","BRICKER HALL, JOHN W    "
0,"0008A","BRICKER HALL, JOHN W    "
0,"0008B","BRICKER HALL, JOHN W    "
0,"0008C","BRICKER HALL, JOHN W    "
0,"0008D","BRICKER HALL, JOHN W    "
0,"0008E","BRICKER HALL, JOHN W    "
0,"0008F","BRICKER HALL, JOHN W    "
0,"0008G","BRICKER HALL, JOHN W    "
0,"0008H","BRICKER HALL, JOHN W    "

我想要的是所有建筑物的所有楼层。

我正在使用cat,awk,sort和uniq来获取此列表,尽管我遇到了问题","在建筑物名称领域,如" BRICKER HALL,JOHN W"它正在抛弃我整个csv世代。

cat Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq > Floors.csv 

如何让awk使用逗号但忽略逗号之间""一个领域?或者,有人有更好的解决方案吗?

根据提供的答案提示awk csv解析器,我能够得到解决方案:

cat Buildings.csv | awk -f csv.awk | awk -F" -> 2|"  '{print $2}' | awk -F"|" '{print $2","$3}' | sort | uniq > floors.csv 

我们想要使用csv awk程序,然后从那里我想使用" - > 2 |"这是基于csv awk程序的格式化。打印$ 2那里只打印csv解析的内容,这是因为程序打印原始行后跟" - > #"其中#是从csv解析的计数。 (即列。)从那里我可以将这个awk csv结果拆分为" |"它是取代逗号的原因。然后排序,uniq和管道输出到文件并完成!

感谢您的帮助。

7 个答案:

答案 0 :(得分:35)

gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq

这是一个非常棒的GNU Awk 4扩展,您可以在其中定义字段模式而不是字段分隔符模式。对CSV有奇迹。 (docs

ETA(感谢mitchus):要删除周围的引号,gsub("^\"|\"$","",$3);如果有更多字段而不仅仅是$3来处理这种方式,只需循环遍历它们 请注意,这种简单的方法不能容忍格式错误的输入,也不能容忍引号之间的一些可能的特殊字符 - 涵盖所有这些特征将超出整齐的单行的范围。

答案 1 :(得分:9)

您从csv.awk获得的额外输出来自演示代码。您打算使用脚本中的函数进行解析,然后根据需要输出。

csv.awk的末尾是{ ... }循环,它演示了其中一个函数。这是输出-> 2|的代码。

相反,只需调用解析函数并执行print csv[1], csv[2]

那部分代码如下:

{
    num_fields = parse_csv($0, csv, ",", "\"", "\"", "\\n", 1);
    if (num_fields < 0) {
        printf "ERROR: %s (%d) -> %s\n", csverr, num_fields, $0;
    } else {
#        printf "%s -> ", $0;
#        printf "%s", num_fields;
#        for (i = 0;i < num_fields;i++) {
#            printf "|%s", csv[i];
#        }
#        printf "|\n";
        print csv[1], csv[2]
    }
}

将其另存为your_script(例如)。

执行chmod +x your_script

cat是不必要的。此外,您可以sort -u代替sort | uniq

您的命令将如下所示:

./yourscript Buildings.csv | sort -u > floors.csv

答案 2 :(得分:6)

我的解决方法是使用以下命令从csv中删除逗号:

decommaize () {
  cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2
}

也就是说,首先用“((”和“引号与”))“替换开头引号,然后用”(无论什么,不管“)代替”“和”无论什么“,然后改变所有剩余的实例”( (“和”))“回到”。

答案 3 :(得分:3)

你可以尝试这个基于awk的csv paser:

http://lorance.freeshell.org/csv/

答案 4 :(得分:2)

您可以使用我编写的名为csvquote的脚本让awk忽略引用字段中的逗号。然后该命令将成为:

csvquote Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq | csvquote -u > Floors.csv

和cut可能比awk更容易:

csvquote Buildings.csv | cut -d, -f1,2 | sort | uniq | csvquote -u > Floors.csv

您可以在此处找到csvquote代码:https://github.com/dbro/csvquote

答案 5 :(得分:0)

完全成熟的CSV解析器(如Perl的Text::CSV_XS)专门用于处理这种奇怪现象。

perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "$f[0],$f[1]" }' file

输入行分为数组@f
字段1是$f[0],因为Perl开始索引为0

输出:

u_floor,u_room
0,00BDF
0,0
0,3
0,5
0,6
0,7
0,8
0,9
0,19
0,20
0,21
0,25
0,27
0,29
0,35
0,45
0,59
0,60
0,61
0,63
0,0006M
0,0008A
0,0008B
0,0008C
0,0008D
0,0008E
0,0008F
0,0008G
0,0008H

我在答案中提供了Text::CSV_XS的更多解释:parse csv file using gawk

答案 6 :(得分:0)

由于问题实际上是要区分CSV字段中的逗号和分隔字段的逗号,我们可以用其他东西替换第一种逗号,以便更容易进一步解析,例如:< / p>

0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL<comma> JOHN W    "

这个gawk脚本(replace-comma.awk)可以做到:

BEGIN { RS = "(.)" } 
RT == "\x022" { inside++; } 
{ if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }

这使用gawk功能将实际记录分隔符捕获到名为RT的变量中。它将每个字符拆分为一个记录,当我们读取记录时,我们将引号(\x022)中遇到的逗号替换为<comma>

FPAT解决方案在一个特殊情况下失败,你有两个转义引号和引号内的逗号,但这个解决方案适用于所有情况,即

§ echo '"Adams, John ""Big Foot""",1' | gawk -vFPAT='[^,]*|"[^"]*"' '{ print $1 }'
"Adams, John "
§ echo '"Adams, John ""Big Foot""",1' | gawk -f replace-comma.awk | gawk -F, '{ print $1; }'
"Adams<comma> John ""Big Foot""",1

作为易于复制粘贴的单行代码:

gawk 'BEGIN { RS = "(.)" } RT == "\x022" { inside++; } { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }'