如何在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编译模块。一旦加载,它们就像任何其他内置命令一样工作!(您可以在源代码树中找到更多信息。;)
当前源代码树(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 内建函数:
然后:
enable -f /usr/lib/bash/csv csv
从那里开始,您可以将csv
用作bash内置命令。
使用我的示例:12,22.45,“你好,”“人”“。”,“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内置或fork到任何二进制文件的组合更快、更强大。
不幸的是,取决于您的系统实现,如果您的bash版本编译时没有使用loadable
,则可能无法正常工作...
带有多行CSV字段的完整示例。
符合RFC 4180的规范,像这样的字符串单个CSV行:
12,22.45,"Hello ""man"",
This is a good day, today!","A, b.",42
应该被拆分为
1 12
2 22.45
3 Hello "man",
This is a good day, today!
4 A, b.
5 42
解析包含多行字段的CSV完整示例脚本
这是一个包含1个标题、4列和3行的小样本文件。由于两个字段包含换行符,因此该文件长度为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[@]}"
numcols=${#headline[@]}
while read -ru $FD line;do
while csv -a row "$line" ; (( ${#row[@]} < numcols )) ;do
read -ru $FD sline || break
line+=$'\n'"$sline"
done
printf "$fieldfmt\\n" "${row[@]}"
done
这可能会呈现为:(我使用了printf "%q"
来表示非可打印字符,如换行符,作为$'\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文件有效,但要注意编码和安全性!例如,该模块无法处理二进制字段!
请仔细阅读csv.c源代码注释和RFC 4180!
关于带引号的多行字段的注意事项
特别是如果多行字段位于最后一列,这种方法将无法正确循环到第二个引号。
因此,在使用csv
模块解析之前,您必须在$line
中检查引号匹配。
您可以在使用bash解析带有不寻常字符、间距、括号和不规则换行符的大型CSV文件中找到一个完整的可工作示例。
awk
来使用$1
、$2
等吗? - BeemerGuycat
使用。 - tripleee