使用 awk 解析 csv 并忽略字段内的逗号
Posted
技术标签:
【中文标题】使用 awk 解析 csv 并忽略字段内的逗号【英文标题】:Parse a csv using awk and ignoring commas inside a field 【发布时间】:2011-05-11 11:08:26 【问题描述】:我有一个 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 程序的格式。 print $2 there 只打印 csv 解析的内容,这是因为程序打印原始行后跟“-> #”,其中 # 是从 csv 解析的计数。 (即列。)从那里我可以在“|”上拆分这个 awk csv 结果whcih 是用它替换逗号的内容。然后排序、uniq 和管道输出到文件并完成!
感谢您的帮助。
【问题讨论】:
这能回答你的问题吗? What's the most robust way to efficiently parse CSV using awk? 【参考方案1】:由于问题实际上是要区分 CSV 字段中的逗号和分隔字段的逗号,我们可以将第一种逗号替换为其他内容,以便进一步解析,例如:
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); '
【讨论】:
【参考方案2】:完全成熟的 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
【讨论】:
【参考方案3】:gawk -vFPAT='[^,]*|"[^"]*"' 'print $1 "," $3' | sort | uniq
这是一个很棒的 GNU Awk 4 扩展,您可以在其中定义字段模式而不是字段分隔符模式。为 CSV 创造奇迹。 (docs)
ETA(感谢 mitchus): 要删除周围的引号,gsub("^\"|\"$","",$3)
;如果有比$3
更多的字段以这种方式处理,只需遍历它们。
请注意,这种简单的方法不能容忍格式错误的输入,也不能容忍引号之间的一些可能的特殊字符——涵盖所有这些超出了简洁的单行代码的范围。
【讨论】:
这是一个很棒的发现!在许多情况下,不需要外部 CSV 库。 太棒了! - 是否也可以对其进行修改,以便在存在时去掉引号。如果字段本身中存在逗号,我的输出只有引号 仅适用于使用 mac 的其他人:OS X 不附带 GAWK,他们从 2007 年开始使用 awk。所以基本上你需要自己安装它brew install gawk
,它确实为 CSV 带来了奇迹.
@nwaltham gsub("^\"|\"$","",$3)
其中 $3 是可能在引号下的字段。 (如果有多个,循环遍历它们。)请注意,这种简单的方法不能容忍引号之间的换行符、引号内的引号,也不能容忍带有不平衡引号的格式错误的输入。涵盖所有这些超出了简洁的单行的范围。
你说得对,对不起。我一次打开了多个页面并评论了错误的答案。我以为我正在回复的是这个:***.com/a/46627337/3737935 对此感到抱歉。【参考方案4】:
您可以使用我编写的名为 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】:我的解决方法是使用以下方法从 csv 中删除逗号:
decommaize ()
cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2
也就是说,首先用 "((" 替换左引号,用 "))" 替换右引号,然后用 "whateverwhatever" 替换 "(("whatever,whatever"))",然后更改所有剩余的 "( (" 和 "))" 回到 ".
【讨论】:
我不明白从 CSV 中删除逗号有何帮助? 然后他发明了以太坊【参考方案6】:您从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
【讨论】:
这很好用,除了 "print csv[1], csv[2]" 实际上应该是 "print csv[0], csv[1]" 谢谢! 知道如何让 awk 摆脱字段上的额外空格而不使用固定宽度吗? “机场测试”我想成为“机场测试” @Chris:空格是单独的问题吗,因为如果我print csv[0], csv[1]
我得到“0 00BDF”而不是“AIRPORT TEST”?
对不起,我没有意识到我修改了输入文件并删除了一列。根据原始问题,您是正确的。干杯。还 sed 修复了我的空白问题。
@Chris:在print csv[0], csv[1]
之前做sub(/ *$/, "", csv[0])
。【参考方案7】:
你可以试试这个基于 awk 的 csv paser:
http://lorance.freeshell.org/csv/
【讨论】:
以上是关于使用 awk 解析 csv 并忽略字段内的逗号的主要内容,如果未能解决你的问题,请参考以下文章