【问题标题】:How to parse a CSV in a Bash script?如何在 Bash 脚本中解析 CSV?
【发布时间】:2010-12-06 08:00:12
【问题描述】:

我正在尝试解析可能包含 100k+ 行的 CSV。这是我的标准:

  1. 标识符的索引
  2. 标识符值

我想检索 CSV 中在给定索引中具有给定值的所有行(以逗号分隔)。

有什么想法,特别考虑性能?

【问题讨论】:

标签: bash csv shell


【解决方案1】:

作为cut- 或awk 的替代方案,您可以使用专门的csvtool aka ocaml-csv

$ csvtool -t ',' col "$index" - < csvfile | grep "$value"

根据文档,它处理转义、引用等。

【讨论】:

  • 我认为 csvtool 是我最好的新朋友。疯狂地认为我试图在 bash 中解析 .csv 文件。只需调用 csvtool 即可替换大量 bash 行来处理解析和转义引号、嵌入逗号等!
  • 很棒的解决方案,但需要用户安装csvtool。当您需要使用标准工具时,这可能会成为问题。
  • 要得到这个东西:sudo apt-get install csvtool on ubuntu 等...然后csvtool --help 就像手册页一样,呃,稀疏。
【解决方案2】:

观看这个 youtube 视频:BASH scripting lesson 10 working with CSV files

CSV 文件:

Bob Brown;Manager;16581;Main
Sally Seaforth;Director;4678;HOME

Bash 脚本:

#!/bin/bash
OLDIFS=$IFS
IFS=";"
while read user job uid location
 do

    echo -e "$user \
    ======================\n\
    Role :\t $job\n\
    ID :\t $uid\n\
    SITE :\t $location\n"
 done < $1
 IFS=$OLDIFS

输出:

Bob Brown     ======================
    Role :   Manager
    ID :     16581
    SITE :   Main

Sally Seaforth     ======================
    Role :   Director
    ID :     4678
    SITE :   HOME

【讨论】:

  • 这看起来没有正确处理引用的值(比如"Bob Brown";"Manager";16581;"Main" 甚至"Bob Brown";"Manager; Director";16581;"Main"
  • 这个答案不符合原始问题的参数,使用特定的值和列索引号。
  • 这个答案在以反斜杠字符结尾的行上也失败(正如我刚刚学到的那样)
【解决方案3】:

第一个原型使用普通的旧 grepcut

grep "${VALUE}" inputfile.csv | cut -d, -f"${INDEX}"

如果速度足够快并且输出正确,那么你就完成了。

【讨论】:

  • +1。此管道不允许冒号转义 (\:) 或字符串引用 ("foo: bar")。但这是解决问题的好方法。
  • 无需在管道上使用 2 个工具。我建议使用 awk。
  • @ghostdog:我不知道 awk,看看例如Nate Kohl 的 awk 回复,我认为这至少更简单。
  • 虽然答案对于某些 CSV 文件是正确的,但恕我直言,这比帮助更具破坏性,因为它鼓励 SO 上的人们更喜欢“单线”命令并愉快地采用它们,而没有意识到与这些相关的问题 (答案也没有警告)。简而言之,您使用特定的文件格式解析器解析某种文件格式。就像您不使用正则表达式来验证 html 一样,而是使用 html 解析器/验证器。这种“单行”适用于这些文件格式的某些特殊情况的事实应该始终使用粗体/下划线字母。
【解决方案4】:

CSV 并不是那么简单。根据您拥有的数据的限制,您可能需要担心带引号的值(可能包含逗号和换行符)和转义引号。

因此,如果您的数据受到足够的限制,可以通过简单的逗号分割很好地逃脱,shell 脚本可以轻松做到这一点。另一方面,如果您需要“正确”解析 CSV,那么 bash 不会是我的首选。相反,我会考虑使用更高级别的脚本语言,例如带有csv.reader 的 Python。

【讨论】:

    【解决方案5】:

    在 CSV 文件中,每个字段以逗号分隔。问题是,一个字段本身可能有一个嵌入的逗号:

    Name,Phone
    "Woo, John",425-555-1212
    

    您确实需要一个提供强大 CSV 支持的库包,而不是依赖于使用逗号作为字段分隔符。我知道像 Python 这样的脚本语言有这样的支持。但是,我对 Tcl 脚本语言很满意,所以我使用的是这种语言。这是一个简单的 Tcl 脚本,可以满足您的要求:

    #!/usr/bin/env tclsh
    
    package require csv 
    package require Tclx
    
    # Parse the command line parameters
    lassign $argv fileName columnNumber expectedValue
    
    # Subtract 1 from columnNumber because Tcl's list index starts with a
    # zero instead of a one
    incr columnNumber -1
    
    for_file line $fileName {
        set columns [csv::split $line]
        set columnValue [lindex $columns $columnNumber]
        if {$columnValue == $expectedValue} {
            puts $line
        }   
    }
    

    将此脚本保存到名为 csv.tcl 的文件中并调用它:

    $ tclsh csv.tcl filename indexNumber expectedValue
    

    说明

    脚本逐行读取 CSV 文件并将该行存储在变量 $line 中,然后将每一行拆分为列列表(变量 $columns)。接下来,它挑选出指定的列并将其分配给 $columnValue 变量。如果匹配,则打印出原始行。

    【讨论】:

      【解决方案6】:

      使用awk

      export INDEX=2
      export VALUE=bar
      
      awk -F, '$'$INDEX' ~ /^'$VALUE'$/ {print}' inputfile.csv
      

      编辑:根据Dennis Williamson's 出色的评论,这可以通过使用-v 开关定义awk 变量来更简洁(和安全)编写:

      awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' inputfile.csv
      

      天啊...有变量和一切,awk 几乎是a real programming language...

      【讨论】:

      • 导出可能是不必要的。你应该使用awk's 变量传递功能,否则引用会变得很麻烦:awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' inputfile.csv
      • 这不会处理带有可能包含换行符的引用字段的重要 CSV 文件。
      【解决方案7】:

      对于数据不包含任何特殊字符的情况,Nate Kohl 和 ghostdog74 提出的解决方案很好。

      如果数据在字段中包含逗号或换行符,awk 可能无法正确计算字段编号,您将得到不正确的结果。

      在我编写的名为 csvquote 的程序(可在https://github.com/dbro/csvquote 获得)的帮助下,您仍然可以使用 awk:

      csvquote inputfile.csv | awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' | csvquote -u
      

      这个程序在带引号的字段中查找特殊字符,并用不会混淆 awk 的非打印字符临时替换它们。然后在 awk 完成后恢复它们。

      【讨论】:

        【解决方案8】:
        index=1
        value=2
        awk -F"," -v i=$index -v v=$value '$(i)==v' file
        

        【讨论】:

          【解决方案9】:

          我一直在寻找一个优雅的解决方案,它支持引用并且不需要在我的 VMware vMA 设备上安装任何花哨的东西。原来这个简单的 python 脚本可以解决问题! (我将脚本命名为 csv2tsv.py,因为它将 CSV 转换为制表符分隔值 - TSV)

          #!/usr/bin/env python
          
          import sys, csv
          
          with sys.stdin as f:
              reader = csv.reader(f)
              for row in reader:
                  for col in row:
                      print col+'\t',
                  print
          

          制表符分隔的值可以使用 cut 命令轻松拆分(无需指定分隔符,制表符是默认值)。这是一个示例用法/输出:

          > esxcli -h $VI_HOST --formatter=csv network vswitch standard list |csv2tsv.py|cut -f12
          Uplinks
          vmnic4,vmnic0,
          vmnic5,vmnic1,
          vmnic6,vmnic2,
          

          在我的脚本中,我实际上将逐行解析 tsv 输出并使用 read 或 cut 来获取我需要的字段。

          【讨论】:

            【解决方案10】:

            sedawk 解决方案可能会更短,但这是 Perl 的解决方案:

            perl -F/,/ -ane 'print if $F[<INDEX>] eq "<VALUE>"`
            

            其中&lt;INDEX&gt; 从 0 开始(0 表示第一列,1 表示第二列,等等)

            【讨论】:

            • 如果你无论如何要使用 perl,肯定有一个 perl csv 库会更好用吗?
            • 对于行为端正的输入? That's not true.
            • @mob 更好并不总是意味着更快。
            【解决方案11】:

            使用原始文本处理工具解析 CSV 将在许多类型的 CSV 输入上失败。

            xsv 是一个可爱且快速 的工具,可以正确执行此操作。要在第三列中搜索所有包含字符串“foo”的记录:

            cat file.csv | xsv search -s 3 foo
            

            【讨论】:

              猜你喜欢
              • 2013-07-24
              • 1970-01-01
              • 1970-01-01
              • 2011-04-24
              • 2011-05-16
              • 1970-01-01
              • 2013-08-30
              • 1970-01-01
              • 2016-09-29
              相关资源
              最近更新 更多