如何在 Bash 脚本中解析 CSV?

Posted

技术标签:

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

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

    标识符的索引 标识符值

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

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

【问题讨论】:

有关强大的 awk 解决方案,请参阅 ***.com/q/45420535/1745001。 【参考方案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

【讨论】:

以上是关于如何在 Bash 脚本中解析 CSV?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用bash脚本从csv文件中读取特定的整数?

BASH - 如何从 CSV 文件中的列中提取数据并将其放入数组中?

在 BASH 脚本中使用“awk”将列添加到 CSV 文件的末尾

如何删除多余的双引号,而不是使用bash脚本在一行文本中打开和关闭双引号

从 bash 或 perl 脚本中读取 CSV [重复]

如何在 Bash 脚本中自动引用 SQL INSERT STATEMENT 的“字符串值”