如何处理由bash脚本读取的CSV文件中的逗号

时间:2012-01-20 10:58:39

标签: 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, Republic。这些值用引号括起来表示其中的字符应该被视为字段的一部分,但我不知道如何解析它以将其考虑在内。

目前我有这个循环:

while IFS=, read province provinceCode criteriaId countryCode country
do
    echo "[$province] [$provinceCode] [$criteriaId] [$countryCode] [$country]"
done < $input

为上面给出的样本数据生成此输出:

[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
["Adygeya] [ Republic"] [RU-AD] [21250] [RU,Russian Federation]

如您所见,第三个条目的解析不正确。我希望它输出

[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]

6 个答案:

答案 0 :(得分:8)

如果你想在 awk 中完成所有操作( GNU awk 4 是此脚本按预期工作所必需的):

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

示例输出:

% cat infile
Australian Capital Territory,AU-ACT,20034,AU,Australia
Piaui,BR-PI,20100,BR,Brazil
"Adygeya, Republic",RU-AD,21250,RU,Russian Federation
% 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
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya, Republic] [RU-AD] [21250] [RU] [Russian Federation]

使用 Perl

perl -MText::ParseWords -lne'
 print join " ", map "[$_]", 
   parse_line(",",0, $_);
  ' infile 

这应该适用于您的awk版本(基于this c.u.s。帖子,也删除了嵌入式逗号)。

awk '{
 n = parse_csv($0, data)
 for (i = 0; ++i <= n;) {
    gsub(/,/, " ", data[i])
    printf "[%s]%s", data[i], (i < n ? OFS : RS)
    }
  }
function parse_csv(str, array,   field, i) { 
  split( "", array )
  str = str ","
  while ( match(str, /[ \t]*("[^"]*(""[^"]*)*"|[^,]*)[ \t]*,/) ) { 
    field = substr(str, 1, RLENGTH)
    gsub(/^[ \t]*"?|"?[ \t]*,$/, "", field)
    gsub(/""/, "\"", field)
    array[++i] = field
    str = substr(str, RLENGTH + 1)
  }
  return i
}' infile

答案 1 :(得分:5)

在[{3}}上查看 @Dimitre的解决方案之后。你可以这样做 -

#!/usr/local/bin/gawk -f

BEGIN {
    FS="," 
    FPAT="([^,]+)|(\"[^\"]+\")"
    }

      {
    for (i=1;i<=NF;i++) 
        printf ("[%s] ",$i);
    print ""
    } 

测试:

[jaypal:~/Temp] cat filename
Australian Capital Territory,AU-ACT,20034,AU,Australia
Piaui,BR-PI,20100,BR,Brazil
"Adygeya, Republic",RU-AD,21250,RU,Russian Federation

[jaypal:~/Temp] ./script.awk  filename
[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia] 
[Piaui] [BR-PI] [20100] [BR] [Brazil] 
["Adygeya, Republic"] [RU-AD] [21250] [RU] [Russian Federation] 

要删除",您可以将输出传递给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] 

答案 2 :(得分:2)

在考虑了这个问题之后,我意识到由于字符串中的逗号对我来说并不重要,因此在解析之前将其从输入中删除会更容易。

为此,我编写了一个sed命令,该命令匹配由包含逗号的双引号括起来的字符串。然后,该命令从匹配的字符串中删除您不想要的位。它通过将正则表达式分成记忆部分来实现这一点。

此解决方案仅适用于字符串在双引号之间包含单个逗号的位置。

未转义的正则表达式

(")(.*)(,)(.*)(")

第一,第三和第五对括号分别捕获开头双引号,逗号和结束双引号。

第二对和第三对括号捕获我们想要保留的字段的实际内容。

sed删除逗号的命令

echo "$input" | sed 's/\(\"\)\(.*\)\(,\)\(.*\)\(\"\)/\1\2\3\4/' 

sed命令删除逗号和双引号

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]

答案 3 :(得分:0)

由于我的系统上有awk稍微过时的版本以及个人偏好坚持使用Bash脚本,我的解决方案略有不同。

我已经生成了一个基于this blog post的实用程序脚本,它解析CSV文件并用您选择的分隔符替换分隔符,以便捕获输出并用于轻松处理数据。该脚本尊重引用的字符串和嵌入的逗号,但会删除它找到的双引号,并且不适用于字段中的转义双引号。

#!/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

只是发布它以防其他人认为它有用。

答案 4 :(得分:0)

如果您可以容忍输出中存在周围的引号,则可以使用我编写的名为csvquote的小脚本来启用awk和cut(以及其他UNIX文本工具)来正确处理包含逗号的引用字段。你像这样包装命令:

csvquote inputfile.csv | awk -F, '{print "["$1"] ["$2"] ["$3"] ["$4"] ["$5"]"}' | csvquote -u

请参阅https://github.com/dbro/csvquote了解代码和文档

答案 5 :(得分:0)

使用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