![在 awk 中将双引号内的逗号转义为字段分隔符](https://linux22.com/image/146971/%E5%9C%A8%20awk%20%E4%B8%AD%E5%B0%86%E5%8F%8C%E5%BC%95%E5%8F%B7%E5%86%85%E7%9A%84%E9%80%97%E5%8F%B7%E8%BD%AC%E4%B9%89%E4%B8%BA%E5%AD%97%E6%AE%B5%E5%88%86%E9%9A%94%E7%AC%A6.png)
我需要向 csv 文件添加两个字段。 csv 字段的分隔符是comma
,并且某些字段位于双引号内。问题是,在双引号字段内,也可能找到逗号。如何用 awk 分割它?
这些字段来自 mongo 导出。这些字段的位置可能会改变,
示例输入 csv,
DateTime,Dealers,Locations,CallEndTime,TotalDuration
"2018-12-27 12:19:14","Dealer1,Dealer2,Dealer3","Gujarat",,67,,
"2018-12-27 12:19:14","Dealer1,Dealer2","Gujarat,Vadodara",,100,
示例输出 csv,
DateTime,Dealers,Locations,CallEndTime,TotalDuration
"2019-01-07 11:35:42","Dealer1,Dealer2,Dealer3","Gujarat","2019-01-07 11:36:51",69,,
"2018-12-27 12:19:14","Dealer1,Dealer2","Gujarat,Vadodara","2018-12-27 12:19:14,78",
awk代码:
BEGIN { FSOFS=","}
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
}
NR>1 {
begSecs = mktime( gensub( /[":-]/, " ", "g", $(f["DateTime"]) ) )
endSecs = begSecs + $(f["TotalDuration"])
$(f["CallEndTime"]) = strftime("%Y-%m-%d %H:%M:%S", endSecs)
}
{print}
我不想将双引号内的逗号视为 FS,我已经看到这可以使用 FPAT 来完成,但我没有任何关于如何在此处使用它的线索,以防万一,
BEGIN { FPAT = "([^,]*)|(\"[^\"]+\")"}
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
}
NR>1 {
begSecs = mktime( gensub(/[":-]/," ","g",$(f["DateTime"])) )
endSecs = begSecs + $(f["TotalDuration"])
$(f["CallEndTime"]) = strftime("%Y-%m-%d %H:%M:%S", endSecs)
}
{print}
答案1
我不会用来awk
解析 csv 文件,最好使用专用工具,例如使用 python csv 模块:
#!/usr/bin/env python3
import csv, shutil
from tempfile import NamedTemporaryFile
from datetime import datetime
from datetime import timedelta
tempfile = NamedTemporaryFile(mode='w', delete=False)
with open('input.csv') as csvfile:
reader = csv.DictReader(csvfile)
writer = csv.DictWriter(tempfile, fieldnames=reader.fieldnames)
writer.writeheader()
for row in reader:
row['CallEndTime']=datetime.strptime(row['DateTime'], '%Y-%m-%d %H:%M:%S') + timedelta(seconds=int(row['TotalDuration']))
writer.writerow(row)
shutil.move(tempfile.name, 'output.csv')
输出.csv:
DateTime,Dealers,Locations,CallEndTime,TotalDuration
2018-12-27 12:19:14,"Dealer1,Dealer2,Dealer3",Gujarat,2018-12-27 12:20:21,67
2018-12-27 12:19:14,"Dealer1,Dealer2","Gujarat,Vadodara",2018-12-27 12:20:54,100
答案2
使用 csvkit >= 1.0.4(当前开发版本),您可以使用csvsql
:
csvsql --query '
update input
set CallEndTime = datetime(DateTime,"+"||TotalDuration||" seconds");
' input.csv
答案3
你的第二个例子几乎可以工作。您只是缺少 a,
作为输出分隔符 ( OFS=","
) 并在新计算的日期两边加上双引号。这有效:
BEGIN { FPAT = "([^,]*)|(\"[^\"]+\")"; OFS=","}
NR==1 {
for (i=1; i<=NF; i++) {
f[$i] = i
}
}
NR>1 {
begSecs = mktime( gensub(/[":-]/," ","g",$(f["DateTime"])) )
endSecs = begSecs + $(f["TotalDuration"])
$(f["CallEndTime"]) = "\"" strftime("%Y-%m-%d %H:%M:%S", endSecs) "\""
}
{print}
对于给出的例子。
但是 csv 的内容比 awk 能够处理的要多得多。正如其他答案已经建议的那样,请使用正确理解 csv 格式的工具。
答案4
BEGIN {
FPAT="\"[^\"]*\"|[^,]*"
}
例如,要提取所有值并将分隔符逗号替换为--
:
awk 'BEGIN { OFS = "--"; FPAT="\"[^\"]*\"|[^,]*"} NR > 1 { print $1, $2, $3, $4, $5 }' input.csv
输出示例:
"2018-12-27 12:19:14"--"Dealer1,Dealer2,Dealer3"--"Gujarat"----67
"2018-12-27 12:19:14"--"Dealer1,Dealer2"--"Gujarat,Vadodara"----100
FS
定义字段分隔符,即定义字段是什么不是。
FPAT
另一方面,定义了一个字段是。
顺便说一句,示例 input.csv 中的第一行有 6 个值,而第二行和标题行表明应该有 5 列。