我有一个 csv 文件和另一个文本文件(比如 file1.csv 和 file2.txt)。文本文件有一列。现在,我想根据文本文件中的数据过滤 csv 文件。例如,
文件 1.csv ------------ 1,a,b,c 2,d,e,f 3,g,d,g 文件2.txt ------------ 1 3
我希望结果是——
1,a,b,c 3,g,d,g
试试这个命令:
awk -F, 'FNR==NR{a[$0];next};$1 in a' file2.txt file1.csv
逻辑很简单:
FOR each line in 'file2.txt' and 'file1.csv'
IF line is from 'file2.txt'
store it to array 'a'
CONTINUE
ENDIF
IF column 1 of line is in 'a'
PRINT line
ENDIF
ENDFOR
对于使用 的改进解决方案grep -f
,请考虑使用bash
过程替换:
grep -f <(sed 's/.*/^&,/' file2.txt) file1.csv
这用于sed
在file2.txt
每行的开头放置一个插入符号并在末尾放置一个逗号,这样当 (GNU?) 将其视为正则表达式时grep
,模式仅匹配行开头的确切字段值. 如果您没有bash
,您可以使用:
sed 's/.*/^&,/' file2.txt | grep -f - file1.csv
但是,当您指定时,并非所有版本都grep
可以读取标准输入-f -
(例如,Mac OS X 上的版本不会,但 GNUgrep
会)。
或者,您可以使用join
适当排序的命令:
join -o 1.1,1.2,1.3,1.4 -t, <(sort file1.csv) <(sort file2.txt)
如果您确信文件已经排序,您可以将其简化为:
join -o 1.1,1.2,1.3,1.4 -t, file1.csv file2.txt
在 Perl 中,您可以使用:
#!/usr/bin/env perl
use strict;
use warnings;
my $file = 0;
my %rows;
while (<>)
{
chomp;
$rows{$_}++ if ($file == 0);
if ($file == 1)
{
my($id) = split /,/;
print "$_\n" if defined $rows{$id};
}
}
continue
{
$file = 1 if eof;
}
可能还有其他方法可以做到这一点;例如,您可能会发现Text::CSV等模块的用途。
但是,此代码读取每一行。如果它来自第一个文件,那么它会创建一个条目$rows{$_}++
来记录看到的数字。顺序和重复无关紧要。在第二个(和后续)文件中,它将第一个逗号分隔的字段从行中拆分出来,并检查是否在第一个文件中找到了该数字;如果是这样,它会打印整行。该continue
块检测代码何时到达第一个文件的 EOF(特别是)并设置$file = 1;
何时到达。它与awk
解同构。这有点冗长。有-a
模式(awk
mode),但是因为这两个文件需要区别对待,让它正常工作有点棘手。
其中,我认为grep -f
解决方案可能是最简洁的,只要file2.txt
不太大(我不确定限制是多少——但可能大得惊人)。
对于通用 CSV 文件操作工具,请考虑csvfix。
对于 Windows 命令版本:
findstr /G:file2.txt file1.csv > result.csv
试试下面的命令:
grep -F -f file2.txt file1.csv
1,a,b,c
3,g,d,g