将 Bash 中的 CSV 读入 Dictionary/Associative 数组
Reading CSV in Bash into a Dictionary/Associative array
我正在尝试将 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)
foobar.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
Cyrus 的 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 文件是通过从 Microsoft Excel 另存为 csv 在 Mac 上创建的。提前感谢您的任何见解。
混合解决方案
对于未来的人来说,这个问题其实是两个问题。第一个是从 Microsoft Excel 为 Mac 工作簿保存我的 CSV 文件。我另存为...“CSV UTF-8”格式(Excel 下拉菜单中列出的第一个 CSV 文件格式)。这会添加额外的字节,这些字节会扰乱 bash 中的读取命令。有趣的是,这些字节不会出现在 cat 命令中(请参阅原始 post 问题描述)。 将 Excel 中的 CSV 保存为“逗号分隔值”(在格式下拉列表的下方),解决了第一个问题。
其次,@Léa Gris 和@glenn jackman 为我指明了正确的方向 我的脚本的修饰符,这有助于一些换行符和回车符 return 字符 出现在 Excel 保存的文件中。
谢谢大家。我花了一整天试图弄清楚这一点。 经验教训:我应该早点转向 Whosebug。
以下是您未获得预期输出的原因:
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#$'737'}"
删除它们
- 最后一行没有换行结束,所以while-read循环只迭代了两次。
read
returns non-zero 如果它不能读取整行,即使它读取一些字符。
- 因为读到returns“false”,while循环结束。
- 可以使用以下方法解决此问题:
while IFS=, read -r name title || [[ -n $title ]]; do ...
#............................. ^^^^^^^^^^^^^^^^^^
- 或者,只修复文件。
结果:
BOM=$'737'
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=$',\r\n' 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=$',\r\n' read -r name title || [ "$name" ]; do
echo "I got:$name|$title"
descriptions[$name]=$title
done < <(
# Filter-out UTF-8 BOM if any
sed $'1s/^737//' 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=$'\r\n,' read -r -d '' -a elements < <(
# Discard the UTF-8 BOM from the input file if any
sed $'1s/^737//' 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
我正在尝试将 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)
foobar.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
Cyrus 的 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 文件是通过从 Microsoft Excel 另存为 csv 在 Mac 上创建的。提前感谢您的任何见解。
混合解决方案
对于未来的人来说,这个问题其实是两个问题。第一个是从 Microsoft Excel 为 Mac 工作簿保存我的 CSV 文件。我另存为...“CSV UTF-8”格式(Excel 下拉菜单中列出的第一个 CSV 文件格式)。这会添加额外的字节,这些字节会扰乱 bash 中的读取命令。有趣的是,这些字节不会出现在 cat 命令中(请参阅原始 post 问题描述)。 将 Excel 中的 CSV 保存为“逗号分隔值”(在格式下拉列表的下方),解决了第一个问题。
其次,@Léa Gris 和@glenn jackman 为我指明了正确的方向 我的脚本的修饰符,这有助于一些换行符和回车符 return 字符 出现在 Excel 保存的文件中。
谢谢大家。我花了一整天试图弄清楚这一点。 经验教训:我应该早点转向 Whosebug。
以下是您未获得预期输出的原因:
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#$'737'}"
删除它们
- 可以使用
- 最后一行没有换行结束,所以while-read循环只迭代了两次。
read
returns non-zero 如果它不能读取整行,即使它读取一些字符。- 因为读到returns“false”,while循环结束。
- 可以使用以下方法解决此问题:
while IFS=, read -r name title || [[ -n $title ]]; do ... #............................. ^^^^^^^^^^^^^^^^^^
- 或者,只修复文件。
结果:
BOM=$'737'
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=$',\r\n' 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=$',\r\n' read -r name title || [ "$name" ]; do
echo "I got:$name|$title"
descriptions[$name]=$title
done < <(
# Filter-out UTF-8 BOM if any
sed $'1s/^737//' 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=$'\r\n,' read -r -d '' -a elements < <(
# Discard the UTF-8 BOM from the input file if any
sed $'1s/^737//' 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