如何在 Bash 中解析 CSV 文件?

我在写一个很长的 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为什么?

217237 次浏览

来自 man页面:

(德语) 分隔符的第一个字符用于终止输入行, 而不是换行。

您正在使用 -d,,它将终止逗号上的输入行。它不会读取该行的其余部分。这就是为什么 $y 是空的。

你需要使用 IFS而不是 -d:

while IFS=, read -r col1 col2
do
echo "I got:$col1|$col2"
done < myfile.csv

注意,对于通用的 CSV 解析,您应该使用一个专门的工具,它可以处理带有内部逗号的引号字段,以及 Bash 无法自行处理的其他问题。这类工具的例子有 cvstoolcsvkit

我们可以使用带引号的字符串解析 csv 文件,并使用以下代码使用 say | 进行分隔

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时稍慢一些。

如果要读取带有一些行的 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"

除了@Dennis Williamson 的回答之外,当第一行包含 CSV 的头部时,跳过它可能会有所帮助:

{
read
while IFS=, read -r col1 col2
do
echo "I got:$col1|$col2"
done
} < myfile.csv

如何在 Bash 中解析 CSV 文件?

对于这个问题来说比较晚,而且 确实提供了新的特性,因为这个问题是关于 的,而且没有一个已经公布的答案显示了这种强大而顺从的 就是这个方法。

使用 可加载模块可加载模块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

编译模块。

下,可以创建、编辑和使用 可加载的类 = “ post-tag”title = “ show questions tag & # 39; c & # 39;”aria-label = “ show questions tag & # 39; c & # 39;”rel = “ tag”aria-labelledby = “ c-container”> c 编译模块 。一旦加载,他们像任何其他 < strong > builtin 工作! !(你可在 源树找到更多资料。;)

当前源代码树(Oct 152021,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.

有一个完整的工作 cvs解析器可以在 examples/loadables目录中使用: 一个 href = “ https://git.savannah.gnu.org/cgit/bash.git/tree/example/loadables/csv.c”rel = “ nofollow noReferrer”> csv.c ! !

在基于 Debian的系统下,您可能必须通过以下方式安装 Bash-builtins软件包

apt install bash-builtins

使用 可加载 bash 内建程序:

然后:

enable -f /usr/lib/bash/csv csv

从那里,您可以使用 csv作为 < strong > bash builtin

用我的样本: 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

这种方式显然是最快和最强大的比使用任何其他 内置程序或任何二进制叉的组合。

不幸的是,取决于您的系统实现,如果您的版本 是编译 没有 loadable,这可能无法工作..。

具有多行 CSV 字段的完整样本。

下面是一个小的示例文件,标题为 1,列为 4,行为 3。因为有两个字段确实包含 < strong > 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"将非打印字符(如 新台词)表示为 $'\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.txtSh .

注:

在这个示例中,我使用 头条新闻来确定 行宽(列数)。如果你的 头条新闻可以持有 新台词,(或者如果你的 CSV 使用超过1头线)。您必须将数字或列作为参数传递给脚本(以及标题行的数量)。

警告:

当然,使用这种方法解析 CSV 并不完美!这个工作为许多简单的 CSV 文件,但关心编码和安全! !对于示例,此模块将不能处理二进制字段!

仔细阅读 C 源代码注释RFC 4180