如何处理bash脚本读取的CSV文件中的逗号
我正在创建一个bash脚本,以从CSV文件生成一些输出(我有1000多个条目,不喜欢手工完成…) CSV文件的内容与此类似:如何处理bash脚本读取的CSV文件中的逗号,bash,scripting,csv,Bash,Scripting,Csv,我正在创建一个bash脚本,以从CSV文件生成一些输出(我有1000多个条目,不喜欢手工完成…) CSV文件的内容与此类似: Australian Capital Territory,AU-ACT,20034,AU,Australia Piaui,BR-PI,20100,BR,Brazil "Adygeya, Republic",RU-AD,21250,RU,Russian Federation 我有一些代码可以使用逗号作为分隔符分隔字段,但有些值实际上包含逗号,例如Adygeya,Repub
Australian Capital Territory,AU-ACT,20034,AU,Australia
Piaui,BR-PI,20100,BR,Brazil
"Adygeya, Republic",RU-AD,21250,RU,Russian Federation
我有一些代码可以使用逗号作为分隔符分隔字段,但有些值实际上包含逗号,例如Adygeya,Republic
。这些值用引号括起来,表示其中的字符应该被视为字段的一部分,但我不知道如何解析它来考虑这一点
目前我有这个循环:
while IFS=, read province provinceCode criteriaId countryCode country
do
echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $input
如您所见,第三个条目的解析不正确。我想让它输出
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
在考虑了这个问题之后,我意识到,由于字符串中的逗号对我来说并不重要,所以在解析之前从输入中删除它会更容易 为此,我设计了一个
sed
命令,它匹配由包含逗号的双引号包围的字符串。然后,该命令从匹配的字符串中删除不需要的位。它通过将正则表达式分割成记忆的部分来实现这一点
此解决方案仅适用于字符串在双引号之间包含一个逗号的情况
未替换的正则表达式是
(")(.*)(,)(.*)(")
第一对、第三对和第五对括号分别捕获开头双引号、逗号和结尾双引号
第二对和第三对括号捕获我们想要保留的字段的实际内容
sed
删除逗号的命令:
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\1\2\3\4/'
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\3/'
tmpFile=$input"Temp"
sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\4/' < $input > $tmpFile
while IFS=, read province provinceCode criteriaId countryCode country
do
echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $tmpFile
rm $tmpFile
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
[Bío-Bío] [CL-BI] [20154] [CL] [Chile]
sed
删除逗号和双引号的命令:
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\1\2\3\4/'
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\3/'
tmpFile=$input"Temp"
sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\4/' < $input > $tmpFile
while IFS=, read province provinceCode criteriaId countryCode country
do
echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $tmpFile
rm $tmpFile
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
[Bío-Bío] [CL-BI] [20154] [CL] [Chile]
更新代码:
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\1\2\3\4/'
echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\3/'
tmpFile=$input"Temp"
sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\2\4/' < $input > $tmpFile
while IFS=, read province provinceCode criteriaId countryCode country
do
echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $tmpFile
rm $tmpFile
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
[Bío-Bío] [CL-BI] [20154] [CL] [Chile]
在看了@Dimitre的解决方案之后。你可以这样做-
#!/usr/local/bin/gawk -f
BEGIN {
FS=","
FPAT="([^,]+)|(\"[^\"]+\")"
}
{
for (i=1;i<=NF;i++)
printf ("[%s] ",$i);
print ""
}
要删除“
,您可以通过管道将输出传输到sed
[jaypal:~/Temp] ./script.awk filename | sed 's#\"##g'
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya, Republic] [RU-AD] [21250] [RU] [Russian Federation]
如果您想在awk中完成这一切(GNU awk 4是此脚本按预期工作所必需的): 这应该适用于您的awk版本(基于c.u.s.post,删除了嵌入的逗号)
awk'{
n=解析csv($0,数据)
对于(i=0;++i,由于我的系统上的awk
版本有些过时,而且我个人喜欢使用Bash脚本,所以我得到了一个稍微不同的解决方案
我已经基于它生成了一个实用程序脚本,该脚本解析CSV文件,并用您选择的分隔符替换分隔符,以便捕获输出并用于轻松处理数据。该脚本尊重带引号的字符串和嵌入的逗号,但将删除它找到的双引号,并且不使用转义双引号n个字段
#!/bin/bash
input=$1
delimiter=$2
if [ -z "$input" ];
then
echo "Input file must be passed as an argument!"
exit 98
fi
if ! [ -f $input ] || ! [ -e $input ];
then
echo "Input file '"$input"' doesn't exist!"
exit 99
fi
if [ -z "$delimiter" ];
then
echo "Delimiter character must be passed as an argument!"
exit 98
fi
gawk '{
c=0
$0=$0"," # yes, cheating
while($0) {
delimiter=""
if (c++ > 0) # Evaluate and then increment c
{
delimiter="'$delimiter'"
}
match($0,/ *"[^"]*" *,|[^,]*,/)
s=substr($0,RSTART,RLENGTH) # save what matched in f
gsub(/^ *"?|"? *,$/,"",s) # remove extra stuff
printf (delimiter s)
$0=substr($0,RLENGTH+1) # "consume" what matched
}
printf ("\n")
}' $input
如果你能容忍在输出中保留周围的引号,你可以使用我编写的一个名为csvquote的小脚本来启用awk和cut(以及其他UNIX文本工具)来正确处理包含逗号的引号字段。你可以这样包装命令:
csvquote inputfile.csv | awk -F, '{print "["$1"] ["$2"] ["$3"] ["$4"] ["$5"]"}' | csvquote -u
有关使用Dimitre的解决方案的代码和文档,请参见。我注意到他的程序忽略了空字段
以下是修复方法:
awk '{
for (i = 0; ++i <= NF;) {
substr($i, 1, 1) == "\"" &&
$i = substr($i, 2, length($i) - 2)
printf "[%s]%s", $i, (i < NF ? OFS : RS)
}
}' FPAT='([^,]*)|("[^"]+")' infile
awk'{
对于(i=0;++我明白了,谢谢@TomWhittock,我将调查该答案给出的链接,我以前从未使用过awk
,因此可能需要对其进行深入研究(为了其他人的利益,该链接为:)你不能用输入中没有的“|”、制表符或其他字符重新导出数据吗?祝你好运。@Sheller不幸的是,我无法控制数据的导出。还可以在Google Group上搜索comp.lang.awk。10年前,有一个关于处理CSV的3个月讨论。一些非常复杂的解决方案。很好uck。在一些特定的情况下,这可能有效,但在很多情况下不会。一个重要的问题是,在sed
中,像*
这样的匹配是贪婪的。感谢您的反馈。根据我的输入,我相信这会很好,但我有兴趣找出如何改进通用解决方案。这会是一个改进吗vement?(“”(^,*)(,)(^“*)(“”
显然sed
不支持惰性匹配,但否定字符类可能会起作用。转义引号也会引起问题,我想谢谢,我不确定这是社区wiki的原因,但我将对此进行检查:)@chrisbunney因为我把dimitre的解决方案作为参考,所以我认为把这个答案归功于它是不合适的。:)刚刚测试过这个,它对我产生的输出和对你产生的输出不一样。事实上,它产生的“坏”是一样的我在报告中描述的输出question@chrisbunney看起来像是awk
版本问题。我在GNUawkV4.0.0
上测试了它。是的,在@Dimitre的帮助下,我的机器上有一个较旧版本的awk,我安装的Debian6似乎没有使用AWK4,我假设这个包会有一个更新的版本awkYou的sion可以尝试我刚才添加的Perl解决方案。接受和+1,因为我认为这是最好的解决方案,即使它不是我在本例中可以使用的解决方案。hi@chrisbunney,我添加了应该与您的awk版本一起使用的版本。