我正在尝试将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
- 第一行的名称不仅包含"foo-1"——其中还有额外的字符。
- 它们可以通过
"${name#$'357273277'}"
去除
- 它们可以通过
- 最后一行不以换行符结尾,因此 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