如何处理bash脚本读取的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

我正在创建一个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,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版本一起使用的版本。