如何在Bash中解析CSV文件?

Use*_*er1 99 linux csv bash

我正在研究一个很长的Bash脚本.我想将CSV文件中的单元格读入Bash变量.我可以解析行和第一列,但不能解析任何其他列.到目前为止,这是我的代码:


  cat myfile.csv|while read line
  do
    read -d, col1 col2 < <(echo $line)
    echo "I got:$col1|$col2"
  done
Run Code Online (Sandbox Code Playgroud)

它只打印第一列.作为额外的测试,我尝试了以下内容:

read -d, x y < <(echo a,b,)

并且$ y是空的.所以我尝试过:

read x y < <(echo a b)

$ y是b.为什么?

Pau*_*ce. 195

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

while IFS=, read -r col1 col2
do
    echo "I got:$col1|$col2"
done < myfile.csv
Run Code Online (Sandbox Code Playgroud)

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

  • 建议的解决方案适用于非常简单的CSV文件,也就是说,如果标头和值没有逗号和嵌入的引号.编写通用的CSV解析器实际上非常棘手(特别是因为有几个CSV"标准").使CSV文件更适合*nix工具的一种方法是将它们转换为TSV(制表符分隔值),例如使用Excel. (6认同)
  • @DennisWilliamson 您应该将分隔符括起来,例如在使用 `;` 时:`while IFS=";" 读取 col1 col2; 做...` (4认同)
  • @thomas.mc.work:对于分号和 shell 特有的其他字符来说确实如此。对于逗号,这是不必要的,我倾向于省略不必要的字符。例如,您始终可以使用大括号指定用于扩展的变量(例如“${var}”),但当不需要时我会忽略它们。对我来说,它看起来更干净。 (2认同)

F. *_*uri 19

如何在 Bash 中解析 CSV 文件?

迟到了这个问题,因为确实提供了新功能,因为这个问题是关于的,而且因为已经发布的答案都没有显示出这种强大且合规的方式来做到这一点

bash使用可加载模块解析 下的 CSV 文件

符合RFC 4180 的字符串,如示例CSV 行

12,22.45,"Hello, ""man"".","A, b.",42
Run Code Online (Sandbox Code Playgroud)

应该被分割为

1  12
2  22.45
3  Hello, "man".
4  A, b.
5  42
Run Code Online (Sandbox Code Playgroud)

bash可加载的.C 编译模块。

下,您可以创建、编辑和使用可加载的编译模块一旦加载,它们就像任何其他内置的一样工作!(您可以在源树中找到更多信息。;)

当前源代码树(2021 年 10 月 15 日,bash V5.1-rc3)确实包含一堆示例:

12,22.45,"Hello, ""man"".","A, b.",42
Run Code Online (Sandbox Code Playgroud)

目录中有一个完整的工作cvs解析器可供使用examples/loadablescsv.c

在基于Debian GNU/Linux 的系统下,您可能需要通过以下方式安装bash-builtins软件包

apt install bash-builtins
Run Code Online (Sandbox Code Playgroud)

使用可加载的 bash-builtins

然后:

enable -f /usr/lib/bash/csv csv
Run Code Online (Sandbox Code Playgroud)

从那里,您可以用作bash 内置csv命令。

以我的样本为例: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
Run Code Online (Sandbox Code Playgroud)

然后循环处理一个文件。

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
Run Code Online (Sandbox Code Playgroud)

内建函数或 fork 任何二进制文件的任何其他组合最快、最强大。

不幸的是,根据您的系统实现,如果您的版本是在没有loadable编译的情况下编译的,则这可能不起作用......

包含多行 CSV 字段的完整示例。

符合RFC 4180 的字符串,如以下单个 CSV 行

12,22.45,"Hello ""man"",
This is a good day, today!","A, b.",42
Run Code Online (Sandbox Code Playgroud)

应该被分割为

1  12
2  22.45
3  Hello "man",
   This is a good day, today!
4  A, b.
5  42
Run Code Online (Sandbox Code Playgroud)

用于解析包含多行字段的 CSV 的完整示例脚本

这是一个小示例文件,包含1 个标题、4列和3行。由于两个字段确实包含换行符,因此文件长度为6行。

1  12
2  22.45
3  Hello, "man".
4  A, b.
5  42
Run Code Online (Sandbox Code Playgroud)

一个小脚本能够正确解析该文件:

#!/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
Run Code Online (Sandbox Code Playgroud)

这可能会呈现:(我曾经printf "%q"将不可打印的字符(例如换行符)表示为$'\n'

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.
Run Code Online (Sandbox Code Playgroud)

您可以在那里找到完整的工作示例:csvsample.sh.txtcsvsample.sh

笔记:

在此示例中,我使用标题行来确定行宽(列数)。如果您的标题行可以包含换行符(或者如果您的 CSV 使用超过 1 个标题行)。您必须将数字或列作为参数传递给脚本(以及标题行数)。

警告:

当然,用这个解析 CSV 并不完美!这适用于许多简单的 CSV 文件,但要关心编码和安全性!例如,该模块将无法处理二进制字段!

仔细阅读csv.c源代码注释RFC 4180

关于带引号的多行字段的注意事项

特别是如果多行字段位于最后一列,此方法将无法正确循环到第二个引号。

为此,您必须$line在使用csv模块进行解析之前检查引号奇偶性。


dog*_*ane 9

man页面:

-d delim delim的第一个字符用于终止输入行,而不是换行符.

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


Mai*_*ish 5

我们可以用带引号的字符串解析 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"
Run Code Online (Sandbox Code Playgroud)

awk将字符串字段解析为变量并tr删除引号。

awk为每个字段执行时稍慢。

  • 使用 Awk 一次处理一行是一种严重的反模式。`awk -F'|' '{ gsub(/"/, ""); 打印 $1, $2 }' "$csvFile"` (2认同)