将 Bash 中的 CSV 读取到字典/关联数组中



我正在尝试将csv文件读入bash关联数组,但没有得到我期望的结果。

使用 Bash 5.0.18

Bellum:fox3-api rocky$ bash --version
GNU bash, version 5.0.18(1)-release (x86_64-apple-darwin19.5.0)

福巴的内容.csv

Bellum:scripts rocky$ cat ./foobar.csv
foo-1,bar-1
foo-2,bar-2
foo-3,bar-3

problem.sh 内容

#!/usr/bin/env bash
declare -A descriptions
while IFS=, read name title; do
echo "I got:$name|$title"
descriptions[$name]=$title
done < foobar.csv
echo ${descriptions["foo-1"]}
echo ${descriptions["foo-2"]}
echo ${descriptions["foo-3"]}

problem.sh 的实际输出

Bellum:scripts rocky$ ./problem.sh
I got:foo-1|bar-1
I got:foo-2|bar-2
bar-2
Bellum:scripts rocky$

期望输出:

I got:foo-1|bar-1
I got:foo-2|bar-2
I got:foo-3|bar-3    
bar-1
bar-2
bar-3

注释请求的输出

Bellum:scripts rocky$ head -n 1 ./foobar.csv | hexdump -C
00000000  ef bb bf 66 6f 6f 2d 31  2c 62 61 72 2d 31 0d 0a  |...foo-1,bar-1..|
00000010
Bellum:scripts rocky$ od -c foobar.csv
0000000  357 273 277   f   o   o   -   1   ,   b   a   r   -   1  r  n
0000020    f   o   o   -   2   ,   b   a   r   -   2  r  n   f   o   o
0000040    -   3   ,   b   a   r   -   3
0000050

赛勒斯的dos2unix变化

#!/usr/bin/env bash

declare -A descriptions
dos2unix < foobar.csv | while IFS=, read name title; do
echo "I got:$name|$title"
descriptions[$name]=$title
done

echo ${descriptions["foo-1"]}
echo ${descriptions["foo-2"]}
echo ${descriptions["foo-3"]}

Cyrus dos2unix 更改的输出

Bellum:scripts rocky$ ./problem.sh
I got:foo-1|bar-1
I got:foo-2|bar-2




Bellum:scripts rocky$

csv 文件是通过从 Excel 中另存为 csv 在 Mac 上制作的Microsoft。 提前感谢您的任何见解。

混合解决方案

对于未来的人们来说,这个问题实际上是两个问题。 第一个是从 Microsoft Excel for Mac 工作簿中保存我的 CSV 文件。 我保存为..."CSV UTF-8"格式(Excel 下拉菜单中列出的第一种 CSV 文件格式)。 这增加了额外的字节,这些字节弄乱了 bash 中的读取命令。 有趣的是,这些字节不会显示在 cat 命令中(请参阅原始帖子问题描述)。将CSV从Excel保存为"逗号分隔值"(在格式下拉列表中更靠后),摆脱了第一个问题。

其次,@Léa Gris 和 @glenn jackman 为我的脚本指明了修饰符的正确方向,这些修饰符有助于处理 Excel 保存文件中存在的一些换行符和回车符

谢谢大家。 我花了一整天的时间试图弄清楚这一点。经验教训:我应该早点转向Stackoverflow。

以下是您无法获得预期输出的原因:

Bellum:scripts rocky$ od -c foobar.csv
0000000  357 273 277   f   o   o   -   1   ,   b   a   r   -   1  r  n
0000020    f   o   o   -   2   ,   b   a   r   -   2  r  n   f   o   o
0000040    -   3   ,   b   a   r   -   3
0000050
  1. 第一行的名称不仅包含"foo-1"——其中还有额外的字符。
    • 它们可以通过"${name#$'357273277'}"去除
  2. 最后一行不以换行符结尾,因此 while-read 循环仅迭代两次。
    • 如果read无法读取整行,即使它读取某些字符,也返回非零值。
    • 由于 read 返回"false",因此 while 循环结束。
    • 这可以通过使用来解决:
      while IFS=, read -r name title || [[ -n $title ]]; do ... 
      #............................. ^^^^^^^^^^^^^^^^^^ 
      
    • 或者,只是修复文件。

结果:

BOM=$'357273277'
CR=$'r'
declare -A descriptions
while IFS=, read name title || [[ $title ]]; do
descriptions["${name#$BOM}"]=${title%$CR}
done < foobar.csv
declare -p descriptions
echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"
declare -A descriptions=([foo-1]="bar-1" [foo-2]="bar-2" [foo-3]="bar-3" )
bar-1
bar-2
bar-3

这将适用于您的输入文件,无论 Unix 或 DOS 换行符如何,无论 UTF-8 BOM 标记如何,也无论最后一行是否在文件末尾之前有换行符:

#!/usr/bin/env bash
declare -A descriptions
# IFS=$',rn' allow to capture either Unix or DOS Newlines
# read -r warrant not to expand  escaped special characters
# || [ "$name" ] will make sure to capture last line
# even if it does not end with a newline marker
while IFS=$',rn' read -r name title || [ "$name" ]; do
echo "I got:$name|$title"
descriptions[$name]=$title
done < <(
# Filter-out UTF-8 BOM if any
sed $'1s/^357353277//' foobar.csv
)
echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"
# A shorter option for debug, is to dump the variable as a declaration
typeset -p descriptions

现在是一种非常紧凑的方法,可以将CSV一次性传输到关联数组中

#!/usr/bin/env bash
# shellcheck disable=SC2155 # Safe generated assignment with printf %q
declare -A descriptions="($(
# Collect all values from file into an array
IFS=$'rn,' read -r -d '' -a elements < <(
# Discard the UTF-8 BOM from the input file if any
sed $'1s/^357353277//' foobar.csv
)
# Format the elements into an Associative array declaration [key]=value 
printf '[%q]=%q ' "${elements[@]}"
))"
echo "${descriptions["foo-1"]}"
echo "${descriptions["foo-2"]}"
echo "${descriptions["foo-3"]}"
# A shorter option for debug, is to dump the variable as a declaration
typeset -p descriptions

问题出在前 3 个字节上,您可以使用以下命令删除它们:

dd bs=1 skip=3 if=foobar.csv of=foobar2.csv

并尝试使用foobar2.csv

最新更新