我有一个使用,
作为分隔符的ASCII文件(例如csv),但是这个字符也出现在带引号的字符串中:
3, "hh,1,foo", foo
"5,,,5", "1,2,3d,,,something ", foo2
test, "col3", foo3
为了避免这种歧义,我想用,
替换;
分隔符。用命令行怎么做? (在linux下)。
更新/额外提问:
使用 sed 或 awk 至少有两个选项。
我的额外问题是哪一个最快?(这对大 csv文件很重要。)
答案 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.