linux 如何在Bash中解析CSV文件?

mtb9vblg  于 2022-11-28  发布在  Linux
关注(0)|答案(6)|浏览(157)

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

wfsdck30

wfsdck301#

您需要使用IFS而不是-d

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

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

ybzsozfc

ybzsozfc2#

man页面:

  • d delim delim的第一个字符用于终止输入行,而不是换行符。
    你使用的是-d,,它将在逗号处结束输入行,它不会读取行的其余部分,这就是为什么$y是空的。
mu0hgdu0

mu0hgdu03#

如何在Bash中解析CSV文件?

现在讨论这个问题已经晚了,因为bash确实提供了新特性,因为这个问题是关于bash的,而且已经发布的答案都没有显示这种强大而兼容的方法准确地做到了这一点

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

    • 符合RFC 4180**,字符串如下所示 * CSV行 *:
12,22.45,"Hello, ""man"".","A, b.",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 * 解析器可供使用:我的天啊!
在基于Debian GNU/Linux的系统下,您可能必须通过以下方式安装bash-builtins软件包:

apt install bash-builtins

使用 * 可加载bash内置 *:

然后道:

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内置函数的任何其他组合或对任何二进制文件的fork最快、最强。
不幸的是,根据您的系统实现,如果您的bash版本是在没有loadable * 的情况下编译 * 的,这可能不起作用...

带有多行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[@]}"

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.txtcsvsample.sh中的一个或多个。

备注:

在这个例子中,我使用 * head line * 来决定 * row width *(列数)。如果你的 * head line * 可以容纳 * newlines *,(或者如果你的CSV使用了不止一个head line)。你必须将number或columns作为参数传递给你的脚本(以及head line的数目)。

警告:

当然,用这个解析CSV并不完美!它可以解析很多简单的CSV文件,但是要注意编码和安全性!!作为示例,这个模块不能处理二进制字段!
仔细阅读csv.c source code commentsRFC 4180!

okxuctiv

okxuctiv4#

我们可以用引号括起来的字符串来解析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,因此速度稍慢。

wnrlj8wa

wnrlj8wa5#

除了来自@Dennis威廉姆森的答案外,当第一行包含CSV的标题时,跳过它可能会有帮助:

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

vom3gejh6#

如果你想读取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"

相关问题