ASCII文件,如何处理带引号的字符串中的分隔符?

时间:2017-11-14 09:15:49

标签: bash csv awk ascii

我有一个使用,作为分隔符的ASCII文件(例如csv),但是这个字符也出现在带引号的字符串中:

3,       "hh,1,foo",            foo
"5,,,5", "1,2,3d,,,something ", foo2
test,    "col3",                foo3

为了避免这种歧义,我想用,替换;分隔符。用命令行怎么做? (在linux下)。

更新/额外提问

使用 sed awk 至少有两个选项。

我的额外问题是哪一个最快?(这对 csv文件很重要。)

4 个答案:

答案 0 :(得分:3)

我认为您的要求是在FPAT中使用GNU Awk的完美用例,

通常,使用FS时,gawk将字段定义为每个字段分隔符之间出现的记录部分。换句话说,FS定义字段不是什么,而不是字段是什么。但是,有时您真的想要按照它们的内容来定义字段,而不是根据它们不是。

最臭名昭着的案例是所谓的逗号分隔值(CSV)数据。如果逗号只分隔数据,则不会出现问题。当其中一个字段包含嵌入的逗号时出现问题。在这种情况下,大多数程序都将该字段嵌入双引号中。

对于此处显示的CSV数据,每个字段都是“任何不是逗号”,或“双引号,任何不是双引号的内容,以及结束双引号。”如果写为一个正则表达式常量(参见Regexp),我们将([^,]+)|([[:space:]]*\"[^\"]+\")。将其写为字符串要求我们避开双引号,导致:

FPAT = "([^,]+)|([[:space:]]*\"[^\"]+\")"

在您的文件上使用它来输出;字符上的去限制文件

awk -v OFS=';' 'BEGIN{FPAT = "([^,]+)|([[:space:]]*\"[^\"]+\")"}{$1=$1}1' file

此外,您可以通过使用系统中的locale设置来加快速度。将区域设置强制设置为C将允许您将字符与单独的ASCII数据集匹配,而不是UTF-8,将其本地传递给命令

LC_ALL=C awk -v OFS=';' 'BEGIN{FPAT = "([^,]+)|([[:space:]]*\"[^\"]+\")"}{$1=$1}1' file

由于FPAT涉及使用标准正则表达式解析器,因此它可能比涉及非正则表达式替换的解析器执行速度慢。

答案 1 :(得分:2)

对于这种不那么复杂的替换,请使用sed

$ cat file
3,       "hh,1,foo",            foo
"5,,,5", "1,2,3d,,,something ", foo2
test,    "col3",                foo3
$ sed -E 's/,([[:space:]]*")/;\1/g;s/("[[:space:]]*),/\1;/g' file
3;       "hh,1,foo";            foo
"5,,,5"; "1,2,3d,,,something "; foo2
test;    "col3";                foo3

甚至更短

# sed -E 's/,(\s*"[^"]*"\s*),/;\1;/g' file
3;       "hh,1,foo";            foo
"5,,,5"; "1,2,3d,,,something "; foo2
test;    "col3";                foo3

perl解决方案将是

# perl -ane  's/,(\s*"[^"]*"\s*),/;$1;/g;print' 47281774
3;       "hh,1,foo";            foo
"5,,,5"; "1,2,3d,,,something "; foo2
test;    "col3";                foo3

答案 2 :(得分:1)

您可以使用awk命令仅在"引用的部分内进行搜索/替换。

第一步是将,替换为_

cat demo.txt | awk 'BEGIN{FS=OFS="\""} {for(i=2;i<NF;i+=2)gsub(",","_",$i)} 1' 

给出了

3,       "hh_1_foo",            foo
"5___5", "1_2_3d___something ", foo2
test,    "col3",                foo3

然后使用更常见的tr命令将,替换为;

tr ',' ';' 

最后一步在&#34;反向&#34;中再次使用 awk 将临时_占位符替换为初始,字符的方法。

把所有东西放在一起我们有:

cat demo.txt | 
awk 'BEGIN{FS=OFS="\""} {for(i=2;i<NF;i+=2)gsub(",","_",$i)} 1' | 
tr ',' ';' | 
awk 'BEGIN{FS=OFS="\""} {for(i=2;i<NF;i+=2)gsub("_",",",$i)} 1'

给出了

3;       "hh,1,foo";            foo
"5,,,5"; "1,2,3d,,,something "; foo2
test;    "col3";                foo3

正如所料。

更新:最快的解决方案?

我使用了3个答案我在206Mb的csv文件上进行了测试(有几次运行来处理缓存效果......),这是我得到的典型结果:

1 /我最初的回答:

time cat avec_vapeur.csv | awk 'BEGIN{FS=OFS="\""} {for(i=2;i<NF;i+=2)gsub(",","_",$i)} 1' |  tr ',' ';' |  awk 'BEGIN{FS=OFS="\""} {for(i=2;i<NF;i+=2)gsub("_",",",$i)} 1'  > /dev/null

real    0m2.488s
user    0m5.025s
sys     0m0.242s

2 /替代 awk 的解决方案:ravindersingh13

time cat avec_vapeur.csv | awk -F"\"" '{for(i=1;i<=NF;i+=2){gsub(/,/,";",$i)}} 1' OFS="\"" > /dev/null

real    0m4.705s
user    0m4.631s
sys     0m0.111s

3 /基于 sed 的解决方案:sjsam

time cat avec_vapeur.csv | sed -E 's/,([[:space:]]*")/;\1/g;s/("[[:space:]]*),/\1;/g' > /dev/null 

real    0m0.174s
user    0m0.118s
sys     0m0.130s

<强> - &GT;明显的赢家是基于sed的解决方案!

我得到的最后一个答案:inian

time cat avec_vapeur.csv |  awk -v OFS=';' 'BEGIN{FPAT = "([^,]+)|([[:space:]]*\"[^\"]+\")"}{$1=$1}1' > /dev/null

real    0m37.507s
user    0m37.463s
sys     0m0.122s

这也是我测试过的最慢的(这里没有判断,只是为了好玩而完成了这些测试!)

更新:我最初误读= inian =,抱歉。如果我理解你,我会添加

LC_ALL=C

加快速度。

现在我明白了:

real    0m20.268s
user    0m20.008s
sys     0m0.087s

速度快但不如 sed 解决方案快。

现在游戏结束了,我不再需要替补(我必须工作一点)

获胜者的最后一句话, perl 解决方案:sjsam

time cat avec_vapeur.csv | perl -ane  's/,(\s*"[^"]*"\s*),/;$1;/g;print' > /dev/null

real    0m0.134s
user    0m0.096s
sys     0m0.104s

甚至比sed 稍快(至少在我的测试中)!

答案 3 :(得分:1)

考虑到您的Input_file将与显示的示例相同,如果是,那么关注awk也可以帮助您。

awk -F"\"" '{for(i=1;i<=NF;i+=2){gsub(/,/,";",$i)}} 1' OFS="\""   Input_file

输出如下。

3;       "hh,1,foo";            foo
"5,,,5"; "1,2,3d,,,something "; foo2
test;    "col3";                foo3

编辑: 此处也添加了非单一形式的解决方案的说明。

awk -F"\"" '{           ##Making " as a field separator as all the liens in Input_file.
for(i=1;i<=NF;i+=2){    ##Starting a for loop here from variable i value 1 to till value of NF(number of fields in a line) incrementing variable i by 2 here, so that we will NOT touch those commas which are coming in side " " here.
  gsub(/,/,";",$i)      ##Now using gsub functionality of awk which will globally substitute comma with semi colon in current fields value.
}}                      ##closing the block of for loop here.
1                       ##awk works on method of condition then action, so mentioning 1 means I am making condition as TRUE and NO action is defined so by default print of current line of Input_file will happen.
' OFS="\"" Input_file   ##Setting OFS(output field separator) as " and mentioning Inut_file name here.