如何在 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】:
第一个原型使用普通的旧 grep
和 cut
:
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】:sed
或 awk
解决方案可能会更短,但这是 Perl 的解决方案:
perl -F/,/ -ane 'print if $F[<INDEX>] eq "<VALUE>"`
其中<INDEX>
从 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 脚本中使用“awk”将列添加到 CSV 文件的末尾