如何在 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 &lt; &lt;(echo a,b,)

$y 是空的。所以我尝试了:

read x y &lt; &lt;(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 自身无法处理的其他问题。此类工具的示例有 cvstoolcsvkit

【讨论】:

建议的解决方案适用于非常简单的 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 文件的数据?

BASH - 如何从 CSV 文件中的列中提取数据并将其放入数组中?

如何使用bash脚本从csv文件中读取特定的整数?

如何在 BASH 中将制表符分隔值 (TSV) 文件转换为逗号分隔值 (CSV) 文件?

如何在 PHP 中解析 CSV 文件

如何在php中上传和解析CSV文件