【问题标题】:Reading CSV in Bash into a Dictionary/Associative array将 Bash 中的 CSV 读入字典/关联数组
【发布时间】:2021-02-07 14:12:57
【问题描述】:

我正在尝试将 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 文件是在 Mac 上通过从 Microsoft Excel 保存为 csv 来制作的。提前感谢您提供任何见解。

混合解决方案

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

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

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

【问题讨论】:

  • 你的代码对我有用;我很好奇数组中到底有什么 => 在 while 循环之后添加 typeset -p descriptions 以查看完整的数组定义;可能还想验证数据文件的内容 => od -c foobar.csv,然后查看输出中除\n以外的任何非打印字符
  • head -n 1 ./foobar.csv | hexdump -C 的输出添加到您的问题(无评论)。
  • 检查 CSV 文件并确保它没有 CR 字符。如果是,请使用dos2unix foobar.csv 修复它。
  • fwiw,发现357 273 277 的一些命中 - 似乎是“utf-8 字节顺序标记”;删除的几个想法(如果在从 excel 中保存文件期间无法删除):thisthis

标签: excel bash csv associative-array carriage-return


【解决方案1】:

这就是您没有得到预期输出的原因:

    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#$'\357\273\277'}" 删除它们
  2. 最后一行不以换行符结束,因此 while-read 循环仅迭代两次。
    • read 如果无法读取整行,则返回非零值,即使它读取了某些字符。
    • 由于 read 返回“false”,while 循环结束。
    • 可以使用以下方法解决此问题:
      while IFS=, read -r name title || [[ -n $title ]]; do ... 
      #............................. ^^^^^^^^^^^^^^^^^^ 
      
    • 或者,只是修复文件。

结果:

BOM=$'\357\273\277'
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

【讨论】:

  • 感谢您的意见。您对“foo-1”之前的额外字符的评论使我进一步调查了我是如何创建文件的以及最终的解决方案(请参阅我在原始帖子底部的编辑)。您关于如何编辑我的读取命令的 cmets 以及 @Léa Gris 解决了我的问题。感谢您抽出宝贵时间发表评论。
【解决方案2】:

这将适用于您的输入文件,无论是 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/^\357\353\277//' 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/^\357\353\277//' 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

【讨论】:

  • 这很有帮助。它解决了一半的问题,我很欣赏展示完整的工作脚本。唯一没有解决的是 Excel 输入文件的问题(请参阅原始帖子底部的编辑)。非常感谢!
  • @dmjones 我添加了自动删除 BOM(如果有),因此您不必担心它会被创建:
  • 你太棒了。
【解决方案3】:

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

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

并尝试使用 foobar2.csv

【讨论】:

  • 您对前三个字节的评论是正确的。后来我确定是什么原因造成的。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 2022-01-17
  • 1970-01-01
  • 2018-03-19
  • 1970-01-01
  • 2021-03-06
  • 1970-01-01
  • 2016-02-24
  • 1970-01-01
相关资源
最近更新 更多