如何在 Bash 中解析 CSV 文件?
Posted
技术标签:
【中文标题】如何在 Bash 中解析 CSV 文件?【英文标题】:How to parse a CSV file in Bash? 【发布时间】:2011-05-16 05:56:40 【问题描述】:我正在编写一个很长的 Bash 脚本。我想将 CSV 文件中的单元格读入 Bash 变量。我可以解析行和第一列,但不能解析任何其他列。到目前为止,这是我的代码:
cat myfile.csv|while read line
do
read -d, col1 col2 < <(echo $line)
echo "I got:$col1|$col2"
done
它只打印第一列。作为附加测试,我尝试了以下方法:
read -d, x y < <(echo a,b,)
$y 是空的。所以我尝试了:
read x y < <(echo a b)
$y 是b
。为什么?
【问题讨论】:
您考虑过awk
使用$1
、$2
等吗?
作为旁注:command command
“剪切”命令行程序就是为此而设计的:ss64.com/bash/cut.html
***.com/questions/36287982/…的可能重复
你想失去useless use of cat
【参考方案1】:
您需要使用IFS
而不是-d
:
while IFS=, read -r col1 col2
do
echo "I got:$col1|$col2"
done < myfile.csv
请注意,对于通用 CSV 解析,您应该使用专门的工具来处理带内部逗号的引用字段,以及 Bash 自身无法处理的其他问题。此类工具的示例有 cvstool
和 csvkit
。
【讨论】:
建议的解决方案适用于非常简单的 CSV 文件,也就是说,如果标题和值没有逗号和嵌入的引号。编写一个通用的 CSV 解析器实际上是相当棘手的(特别是因为有几个 CSV“标准”)。使 CSV 文件更适合 *nix 工具的一种方法是将它们转换为 TSV(制表符分隔值),例如使用 Excel。 @Zsolt:没有理由应该是这种情况。您必须有错字或杂散的非打印字符。 @DennisWilliamson 您应该附上分隔符,例如使用;
时:while IFS=";" read col1 col2; do ...
@thomas.mc.work:对于分号和其他 shell 特殊字符,情况确实如此。在逗号的情况下,它不是必需的,我倾向于省略不必要的字符。例如,您始终可以使用花括号指定扩展变量(例如$var
),但在不需要时我会省略它们。对我来说,它看起来更干净。
@DennisWilliamson,从一段时间以来,bash 源代码树提供了一个可加载的内置 csv 解析器!看看my answer!当然有一些限制......【参考方案2】:
来自man
页面:
-d 分隔符 delim 的第一个字符用于终止输入行, 而不是换行符。
您正在使用-d,
,它将终止逗号上的输入行。它不会读取该行的其余部分。这就是 $y 为空的原因。
【讨论】:
【参考方案3】:我们可以解析带有引号的字符串并由say |分隔的csv文件。使用以下代码
while read -r line
do
field1=$(echo "$line" | awk -F'|' 'printf "%s", $1' | tr -d '"')
field2=$(echo "$line" | awk -F'|' 'printf "%s", $2' | tr -d '"')
echo "$field1 $field2"
done < "$csvFile"
awk
将字符串字段解析为变量,tr
删除引号。
awk
对每个字段执行时稍慢。
【讨论】:
好,你也可以用逗号(,) 使用 Awk 一次处理一行是一种严重的反模式。awk -F'|' ' gsub(/"/, ""); print $1, $2 ' "$csvFile"
【参考方案4】:
如何在 Bash 中解析 CSV 文件?
这个问题迟到了,因为bash 确实提供了新功能,因为这个问题与bash 有关,而且因为已经发布的答案都没有显示出这种强大且合规的方式正是这样做 .
使用可加载模块解析bash
下的CSV文件
符合RFC 4180,像这样的字符串示例CSV 行:
12,22.45,"Hello, ""man"".","A, b.",42
应该拆分为
1 12
2 22.45
3 Hello, "man".
4 A, b.
5 42
bash 可加载 .C 编译模块。
在bash 下,您可以创建、编辑和使用可加载的c 编译模块。加载后,它们就像任何其他内置一样工作! (您可以在source tree 找到更多信息。;)
当前的源代码树(2021 年 10 月 15 日,bash V5.1-rc3)确实包含一堆样本:
accept listen for and accept a remote network connection on a given port
asort Sort arrays in-place
basename Return non-directory portion of pathname.
cat cat(1) replacement with no options - the way cat was intended.
csv process one line of csv data and populate an indexed array.
dirname Return directory portion of pathname.
fdflags Change the flag associated with one of bash's open file descriptors.
finfo Print file info.
head Copy first part of files.
hello Obligatory "Hello World" / sample loadable.
...
tee Duplicate standard input.
template Example template for loadable builtin.
truefalse True and false builtins.
tty Return terminal name.
uname Print system information.
unlink Remove a directory entry.
whoami Print out username of current user.
examples/loadables
目录中有一个完整的工作 cvs
解析器可供使用:csv.c!!
在基于Debian GNU/Linux的系统下,您可能需要安装bash-builtins包
apt install bash-builtins
使用可加载的 bash-builtins:
然后:
enable -f /usr/lib/bash/csv csv
从那里,您可以使用 csv
作为 bash 内置。
我的样本:12,22.45,"Hello, ""man"".","A, b.",42
csv -a myArray '12,22.45,"Hello, ""man"".","A, b.",42'
printf "%s\n" "$myArray[@]" | cat -n
1 12
2 22.45
3 Hello, "man".
4 A, b.
5 42
然后循环处理一个文件。
while IFS= read -r line;do
csv -a aVar "$line"
printf "First two columns are: [ '%s' - '%s' ]\n" "$aVar[0]" "$aVar[1]"
done <myfile.csv
与使用bash 内置函数的任何其他组合或任何二进制文件的分叉相比,这种方式显然是最快和最强大的。
很遗憾,根据您的系统实现,如果您的bash 版本在编译时没有loadable
,这可能不起作用...
包含多行 CSV 字段的完整示例。
这是一个包含 1 个标题、4 列和 3 行的小示例文件。因为两个字段确实包含 newline,所以文件的长度为 6 行。
Id,Name,Desc,Value
1234,Cpt1023,"Energy counter",34213
2343,Sns2123,"Temperatur sensor
to trigg for alarm",48.4
42,Eye1412,"Solar sensor ""Day /
Night""",12199.21
还有一个能够正确解析这个文件的小脚本:
#!/bin/bash
enable -f /usr/lib/bash/csv csv
file="sample.csv"
exec FD<"$file"
read -ru $FD line
csv -a headline "$line"
printf -v fieldfmt '%-8s: "%%q"\\n' "$headline[@]"
while read -ru $FD line;do
while csv -a row "$line" ; (($#row[@]<$#headline[@])) ;do
read -ru $FD sline || break
line+=$'\n'"$sline"
done
printf "$fieldfmt\\n" "$row[@]"
done
这是我的渲染图:(我使用printf "%q"
将newlines 等不可打印字符表示为$'\n'
)
Id : "1234"
Name : "Cpt1023"
Desc : "Energy\ counter"
Value : "34213"
Id : "2343"
Name : "Sns2123"
Desc : "$'Temperatur sensor\nto trigg for alarm'"
Value : "48.4"
Id : "42"
Name : "Eye1412"
Desc : "$'Solar sensor "Day /\nNight"'"
Value : "12199.21"
您可以在此处找到完整的工作示例:csvsample.sh.txt 或 csvsample.sh.
警告:
当然,使用它来解析 CSV 并不完美!这适用于许多简单的 CSV 文件,但要注意编码和安全性!例如,此模块将无法处理二进制字段!
仔细阅读csv.c source code comments和RFC 4180!
【讨论】:
当然,解析bash下的csv并不完美:csv
loadable将无法处理二进制字段,你可能会遇到encoding 问题和/或安全 问题...仔细阅读RFC 4180!!!【参考方案5】:
除了@Dennis Williamson 的回答之外,当第一行包含 CSV 的标头时,跳过第一行可能会有所帮助:
read
while IFS=, read -r col1 col2
do
echo "I got:$col1|$col2"
done
< myfile.csv
【讨论】:
【参考方案6】:如果您想读取带有某些行的 CSV 文件,那么这是解决方案。
while IFS=, read -ra line
do
test $i -eq 1 && ((i=i+1)) && continue
for col_val in $line[@]
do
echo -n "$col_val|"
done
echo
done < "$csvFile"
【讨论】:
以上是关于如何在 Bash 中解析 CSV 文件?的主要内容,如果未能解决你的问题,请参考以下文章
BASH - 如何从 CSV 文件中的列中提取数据并将其放入数组中?