我正在创建一个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]
答案 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