如何在Bash脚本中解析CSV?

41

我想解析一个可能包含10万行的CSV文件。这是我的要求:

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

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

您有什么建议,特别考虑性能?


1
对于一个强大的awk解决方案,请参见https://dev59.com/XlcO5IYBdhLWcg3wrTcs。 - Ed Morton
13个回答

60
作为对基于cutawk的一行代码的替代方案,您可以使用专业的csvtool,也称为ocaml-csv
$ csvtool -t ',' col "$index" - < csvfile | grep "$value"

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

6
我认为csvtool是我的新得力助手。想想我以前曾试图在bash中解析.csv文件,真是疯狂。现在用一个csvtool的调用取代了许多处理解析和转义引号、嵌入逗号等的bash代码行! - Stéphane
1
很棒的解决方案,但需要用户安装 csvtool。当您需要使用标准工具时,这可能会成为一个问题。 - BugHunterUK
2
要在Ubuntu等系统上获取此工具:sudo apt-get install csvtool,然后运行csvtool --help,因为手册内容有点少。 - ErichBSchulz

43
请查看这个YouTube视频:BASH脚本课程10:使用CSV文件 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

6
看起来它似乎无法正确处理带引号的值(例如:"Bob Brown";"Manager";16581;"Main" 或者 "Bob Brown";"Manager; Director";16581;"Main")。 - Paŭlo Ebermann
1
这个答案不符合原问题的参数,使用了特定的值和列索引号。 - Mr. Lance E Sloan
1
这个答案也无法处理以反斜杠字符结尾的行(正如我刚学到的那样)。 - Peter Russell

29

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

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

如果速度足够快且输出正确,则完成。


2
+1. 这个流水线不允许冒号转义(\:)或字符串引用("foo: bar")。但这是解决问题的一种好而简单的方式。 - Andrey Vlasovskikh
1
不需要在管道中使用两个工具。我建议使用 awk。 - ghostdog74
@ghostdog:我不懂awk,看了Nate Kohl的awk回复,我认为这至少可以被归类为更简单的方法。 - unwind
2
虽然对于某些CSV文件来说,答案是正确的,但在我看来,它比有帮助更有害,因为它鼓励SO上的人们更喜欢“一行命令”,并且在没有意识到与之相关的问题的情况下就轻松地采用它们(答案也没有警告这些问题)。简而言之,您需要使用特定的文件格式解析器来解析某个文件格式。就像您不使用正则表达式来验证HTML,而是使用HTML解析器/验证器一样。这样的“一行命令”适用于这些文件格式的某些特殊情况,应该始终以粗体/下划线的形式呈现。 - Mladen B.

14

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

因此,如果你的数据受限制,可以轻松地使用简单的逗号分割,shell脚本可以完成这项任务。另一方面,如果你需要“正确”解析CSV,则Bash不会是我的首选。相反,我会考虑使用更高级别的脚本语言,例如Python与csv.reader


11

在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变量。如果有匹配项,则打印出原始行。


9

使用 awk:

export INDEX=2
export VALUE=bar

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

编辑:根据Dennis Williamson的优秀评论,使用-v开关定义awk变量可以使其更加清晰(和安全):

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

天啊...有了变量和其他东西,awk 几乎成为一种真正的编程语言...


4
出口很可能是不必要的。你应该使用awk的变量传递功能,否则引号可能会让人困惑:awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' inputfile.csv - Dennis Williamson
2
这无法处理包含换行符的带引号字段的非平凡CSV文件。 - tripleee

5

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

如果数据字段中包含逗号或换行符,则awk可能无法正确计数字段编号,导致结果不正确。

您仍然可以使用awk,并借助我编写的一个名为csvquote的程序(可在https://github.com/dbro/csvquote上获得)来实现:

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

这个程序会查找引号字段内的特殊字符,并且暂时替换成不可打印的字符,以避免混淆 awk。在 awk 完成后,这些字符将被恢复。


3
index=1
value=2
awk -F"," -v i=$index -v v=$value '$(i)==v' file

2

我正在寻找一种优雅的解决方案,支持引用,并且不需要在我的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获取我需要的字段。


2
使用基本文本处理工具解析CSV将无法处理许多类型的CSV输入。 xsv是一个可爱且快速的工具,可以正确地处理CSV。要搜索所有包含字符串“foo”的记录,请在第三列中执行以下操作:
cat file.csv | xsv search -s 3 foo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接