使用grep或其他工具搜索大型CSV文件

afdcj2ne  于 9个月前  发布在  其他
关注(0)|答案(6)|浏览(101)

我有一个4 gig CSV,我试图搜索以获得CSV的子集。我有一个文件CSV文件,其中包含我要搜索的关键字(这些关键字将在大CSV的第一列)。
我尝试了这一行,但它最终花了一个多小时才完成。我需要使用tr来摆脱windows返回char。

LC_ALL=C grep -F -i -f <(tr -d '\r' < keywords.csv) big_csv.csv > output.csv

字符串
有没有什么我可以优化的地方?有没有什么我遗漏的地方?使用awk或者其他工具会更好?我甚至想过排序,然后将大的csv按第一行分割,这样当我搜索的时候,我就可以按文件名搜索关键字,然后将其附加到一个新文件中。有没有最佳实践?我试图尽可能地使其成为POSIX
这里所要求的是一些样本数据。

ADLV,-1.741774,0.961072,-0.751392,-0.935572,-2.269994,1.081103,-0.831244,1.540083,0.474326,-1.322924,2.199037,-0.919939,0.641496,-0.584152,0.729028,0.608351,-0.522026,0.966026,-0.793949,-1.623368,1.16177,-0.642438,-0.675811,-0.214964,-2.263053,2.188642,0.302449,0.770106


第一行将有多个条目。
有更多的数据在行中,但它太长,无法张贴在这里。
关键字文件如下所示

ADLV
ADVG


在最多的关键字.csv将有1,000个关键字。他们都将是每个关键字4个字母。
以下是示例数据https://gist.github.com/fishnibble/9d95658c352a1acab3cec3e965defb3f的要点

hc8w905p

hc8w905p1#

使用任何awk,听起来你所需要的就是:

awk -F, 'NR==FNR{sub(/\r$/,""); keys[$1]; next} $1 in keys' keywords.csv big_csv.csv

字符串
sub(/\r$/,"")在那里是因为你的代码中有tr -d '\r' < keywords.csv-如果你的关键字文件中没有DOS行结束符,那么你不需要它。
我看到你在grep命令中也有-i,这意味着你需要使匹配的大小写不敏感-如果是这样,那么这就是你需要的,仍然使用任何awk:

awk -F, '{key=tolower($1)} NR==FNR{sub(/\r$/,""); keys[key]; next} key in keys' keywords.csv big_csv.csv


您尝试的grep行不仅需要很长时间才能完成,而且还可能产生不正确的输出,因为它搜索big_csv的整行,而不仅仅是第一个字段,因此如果关键字出现在行中的其他位置,它将生成错误匹配,如果您想要的关键字碰巧是其他关键字的子字符串,它也将生成错误匹配。

y1aodyip

y1aodyip2#

这就是你在awk中的做法。我很想看看持续时间是长了还是短了。

awk -F, '
  NR==FNR { words[$1]; next }
  $1 in words
' keywords.csv big_csv.csv > output.csv

字符串
它的工作原理是使用常见的“第一个文件”测试将第一个文件阅读到一个数组中,然后将该数组作为第二个文件的键进行检查。
我可以进一步更新,如果你添加更多的细节到你的问题。

vbkedwbf

vbkedwbf3#

假设条件:

  • 第一个字段(两个文件)不包含逗号,并且 not 用双引号括起来
  • 来自keywords.csv的数据可能具有windows/dos行结尾(\r

设置:

$ cat keywords.csv                         # run through unix2dos to add "\r"
ADLV
ADVG

$ cat big_csv.csv
ADLV,-1.741774,0.961072,-0.751392,-0.935572,-2.269994,1.081103,-0.831244,1.540083,0.474326,-1.322924,2.199037,-0.919939,0.641496,-0.584152,0.729028,0.608351,-0.522026,0.966026,-0.793949,-1.623368,1.16177,-0.642438,-0.675811,-0.214964,-2.263053,2.188642,0.302449,0.770106
WXYZ,-1.741774,0.961072,-0.751392,-0.935572,-2.269994,1.081103,-0.831244,1.540083,0.474326,-1.322924,2.199037,-0.919939,0.641496,-0.584152,0.729028,0.608351,-0.522026,0.966026,-0.793949,-1.623368,1.16177,-0.642438,-0.675811,-0.214964,-2.263053,2.188642,0.302449,0.770106

字符串
一个基本的awk方法:

awk -F, '                                  # input field delimiter is a comma; for 1st file this implies entire line == 1st field
FNR==NR { sub(/\r$/,""); a[$0]; next }     # 1st file: strip off "\r", save line as index in array a[]; skip to next line of input (from 1st file)
$1 in a                                    # 2nd file: if 1st field is an index in array a[] then print current line to stdout
' keywords.csv big_csv.csv > output.csv


这将产生:

$ cat output.csv
ADLV,-1.741774,0.961072,-0.751392,-0.935572,-2.269994,1.081103,-0.831244,1.540083,0.474326,-1.322924,2.199037,-0.919939,0.641496,-0.584152,0.729028,0.608351,-0.522026,0.966026,-0.793949,-1.623368,1.16177,-0.642438,-0.675811,-0.214964,-2.263053,2.188642,0.302449,0.770106

eqfvzcg8

eqfvzcg84#

假设:密钥在两个文件的第1列中(big.csvkeys.txt来自示例数据)

awk 'NR==FNR { a[$1] = 1; next } a[$1] {print $1,$2}' keys.txt FS=\, big.csv

字符串
给出:

SDGA -1.678247
SDSV -2.140182
WDGV -1.31453


(for为了可读性,我只是打印了前两列用于匹配密钥)
我不是一个母语为英语的人,所以对于NR,FNR,数组和这里发生的所有事情的解释,我想参考this discussion
希望这有帮助!

wz1wpwve

wz1wpwve5#

在Ruby中也可以这样做:

ruby -e '
# split(/\R/) works the same with DOS or Unix line endings
keys=File.open(ARGV[0]).read.split(/\R/).map(&:downcase).to_set
File.open(ARGV[1]).each_line{|line| 
    tst=line.split(/,/,2)[0].downcase
    puts line if keys.include?(tst)
}
' sample_keyword.csv sample_input.csv >out.csv

字符串
用Ruby做基本的例子并没有什么特别的优势。事实上,如果实际的使用只是你所描述的那样,我会用awk来做。Ruby可能会更快,但通常不会。
然而,你可以做一些事情,比如输出到不同的格式(JSON、XML、复杂的CSV),这些在awk中是有挑战性的,在Ruby中更容易完成。
你可以在Ruby内部复制curl,并直接阅读你的gist示例:

ruby -e '
require "net/http"
require "uri"

uri1 = URI.parse("https://gist.githubusercontent.com/fishnibble/9d95658c352a1acab3cec3e965defb3f/raw/21fc5153a0b78cdb3eab88c72d700cdf74f20ae7/sample_keyword.csv")
keys = Net::HTTP.get(uri1).split(/\R/).map(&:downcase).to_set

# This can be done in a streaming mode for huge data...
uri2 = URI.parse("https://gist.githubusercontent.com/fishnibble/9d95658c352a1acab3cec3e965defb3f/raw/21fc5153a0b78cdb3eab88c72d700cdf74f20ae7/sample_input.csv")
Net::HTTP.get(uri2).split(/\R/).each{|line|
    tst=line.split(/,/,2)[0].downcase
    puts line if keys.include?(tst)
}' >out.csv


这就是你想要Ruby / Python / Perl的地方,因为在awk中很难做到这一点。你也可以挂载外部服务器或读取ftp等,这些都是awk * 单独 * 的挑战。

ubby3x7f

ubby3x7f6#

有什么我可以优化的吗?有什么我遗漏的吗?
您命令GNU grep在整行中查找关键字。这是不必要的,因为您希望在第一列中找到具有该关键字的行,对于第一列中没有引号的CSV,这意味着行以关键字开头,后跟逗号(,)字符。
使用awk或其他工具会更好吗?
如果你时间有限,你应该准备一个更小的例子,然后测量各种解决方案。由于awk解决方案已经显示,我将提出GNU sed解决方案,通过修改你的关键字文件,比如keywords.txt

ADLV
ADVG

字符串
通过执行sed -e 's/^/\/^/' -e 's/[\r\n]*$/,\/p/' keywords.txt > file.sed

/^ADLV,/p
/^ADVG,/p


这将创建file.sed,可以如下使用

sed -n -f file.sed file.csv


说明:-n解除默认打印操作-f file.sed执行file.sed中的命令,在这种情况下,这些只是p打印。

  • (在GNU sed 4.8中测试)*

相关问题