从文本文件中选择在另一个文件中列出ID的行


13

我在unix shell中使用了很多grep awk排序功能,以处理中等大小(大约10M-100M行)的制表符分隔的列文本文件。在这方面,unix shell是我的电子表格。

但是我有一个巨大的问题,那就是选择具有ID列表的记录。

如果table.csv文件的格式id\tfoo\tbar...ids.csvID列表相同,请仅从table.csvID中存在的ID中选择记录ids.csv

一种/programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids,但带有shell,而不是perl。

grep -F如果id为可变宽度,显然会产生误报。 join是我永远无法弄清楚的实用程序。首先,它需要按字母顺序排序(我的文件通常按数字排序),但是即使那样,我也无法在不抱怨顺序错误和跳过某些记录的情况下使它起作用。所以我不喜欢它。^id\t当id数量很大时,针对带有-s的文件的grep -f 非常慢。 awk麻烦

有什么好的解决方案吗?制表符分隔文件的任何特定工具?额外的功能也将是最受欢迎的。

UPD:已更正sort->join


如果执行grep -f速度太慢,那么维持这种策略听起来会比其应有的麻烦还多,因为同样的O(N * M)性能问题,变化可能会成为危险。也许您最好花时间学习如何使用规范化的 SQL DB ...
goldilocks 2014年

1
为什么不使用您链接的问题中的Perl脚本?或者,应该可以在中编写类似的脚本awk
cjm 2014年

Bash 4具有关联数组,这是在perl示例中规避嵌套循环所需要的。
goldilocks 2014年

1
sort可以进行各种排序,数字,字母排序和其他排序。请参阅man sort
terdon

我在这里有一个查询,如果要从中提取数据的源文件是非定界文件,我们该怎么做

Answers:


19

我想您的意思grep -f不是,grep -F但实际上您需要同时使用和和-w

grep -Fwf ids.csv table.csv

您得到误报的原因是(我想,您没有解释),因为如果一个ID可以包含在另一个ID中,则将同时打印两个ID。-w消除了此问题,-F并确保将您的模式视为字符串,而不是正则表达式。来自man grep

   -F, --fixed-strings
          Interpret PATTERN as a  list  of  fixed  strings,  separated  by
          newlines,  any  of  which is to be matched.  (-F is specified by
          POSIX.)
   -w, --word-regexp
          Select  only  those  lines  containing  matches  that form whole
          words.  The test is that the matching substring must  either  be
          at  the  beginning  of  the  line,  or  preceded  by  a non-word
          constituent character.  Similarly, it must be either at the  end
          of  the  line  or  followed by a non-word constituent character.
          Word-constituent  characters  are  letters,  digits,   and   the
          underscore.

   -f FILE, --file=FILE
          Obtain  patterns  from  FILE,  one  per  line.   The  empty file
          contains zero patterns, and therefore matches nothing.   (-f  is
          specified by POSIX.)

如果您的误报是因为ID可以出现在非ID字段中,请循环遍历您的文件:

while read pat; do grep -w "^$pat" table.csv; done < ids.csv

或者,更快:

xargs -I {} grep "^{}" table.csv < ids.csv

就个人而言,我会这样做perl

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv

1
+1但是:如果存在与ID完全匹配的潜在误报,而不是在ID列中怎么办?如果不能^与-F一起使用,则不能专门针对第一列。
goldilocks 2014年

@goldilocks如果它们完全匹配,则它们不是误报。我明白您的意思,但是在那种情况下,OP应该显示其输入文件。
terdon

^id\tOP中的位暗示id可能在另一列中发生。如果不是,则无所谓。
goldilocks 2014年

@goldilocks公平点,答案已编辑。
terdon

我们过去这样做的方式是创建临时文件(使用awk或sed),并添加一个唯一字符(例如,control-A)来分隔我们要搜索的字段,然后使用grep -F -f temppatternfile temptargetfile | TR -d '\ 001'
马克Plotnick

7

join实用程序是您想要的。它确实要求对输入文件进行词法排序。

假设您的shell是bash或ksh:

join -t $'\t' <(sort ids.csv) <(sort table.csv)

无需排序,通常的awk解决方案是

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv

正如我尝试过但最终未能传达的那样,加入是一种争执。不太适合我。
alamar 2014年

1
join并不是一团糟:您的话语是您无法弄清楚。开放您的思想并学习。您获得了什么输出,这与您的预期有何不同?
glenn jackman 2014年

+1,这是的工作join
don_crissti 2015年

awk就我的目的而言,这里的解决方案非常快捷有效(我从具有约1亿行的文件中提取几百个子集)
Luke

2

对这些问题的答案这太问题帮我周围加入niggles得到。本质上,在对文件进行排序以准备将其发送加入时,您需要确保根据要加入的列进行排序。因此,如果这是第一个,则需要告诉它文件中的分隔符是什么,并且希望它在第一个字段(仅第一个字段)上排序。否则,例如,如果第一个字段的宽度可变,则分隔符和其他字段可能会开始影响排序顺序。

因此,请使用sort的-t选项来指定您的分隔字符,并使用-k选项来指定字段(请记住,您需要一个开始和结束字段-即使它是相同的-否则它将根据该字符进行排序到行的结尾)。

因此,对于像这样的问题中以制表符分隔的文件,以下代码应该可以正常工作(感谢 glenn对结构的回答):

join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv

(作为参考,-d标志表示字典排序。您可能还想使用-b标志忽略前导空格,请参见man sortman join)。

作为更一般的示例,假设您要合并两个逗号分隔的文件- input1.csv第三列和input2.csv第四列。你可以用

join -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv

-1-2选项中,分别指定要在第一输入文件和第二输入文件中加入的字段。


0

您还可以使用ruby做类似的事情:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.