如何在 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!