优化大文件上的grep操作

时间:2019-08-31 03:10:07

标签: linux bash grep do-while

我有两个文件list.txtpurchaselist.txt,它们很大,正在尝试获取最新的购买详细信息(购买清单中有重复项)。

让我们说以下是文件内容:

list.txt

1111
2222
3333

purchaselist.txt

0001 1111 210.00 abcd 10 A 151234 181234 .... 
0011 1111 300.00 abcd 10 A 151000 181222 ....
0022 2222 110.00 abcd 10 E 151111 181000 ....
0099 2222 200.00 abcd 10 A 151222 181999 ....
0033 3333 110.00 abcd 10 A 151000 181222 ....
0044 0044 500.00 abcd 10 A 151999 181333 ....
8899 4444 800.00 abcd 10 A 153333 181777 ....

我使用grep和一个简单的do while循环来执行此操作。这是我的命令:

while read line; do tac purchaselist.txt | grep -m1 $line; done < list.txt >> result.txt

我的预期输出是,已经变得像这样:

0011 1111 300.00 abcd 10 A 151000 181222 ....
0099 2222 200.00 abcd 10 A 151222 181999 ....
0033 3333 110.00 abcd 10 A 151000 181222 ....

上面的输出是通过从我使用过purchaselist.txt的{​​{1}}文件中选择最新行而得出的。 tac中的值在list.txt中显示为第18列。这里的问题是文件很大。 purchaselist.txt包含580k条记录,并在具有约170万条记录的list.txt中查找这些记录。上面的脚本已经运行了将近20个小时,还没有达到一半。如何在这里优化处理时间?

3 个答案:

答案 0 :(得分:2)

该脚本很慢,因为对于list.txt中的每个单词,您都读取了整个purchaselist.txt,对于您而言,它将被读取580K次。此外,bash不能在大型迭代中快速运行。

如果可以接受其他方法,则可以使用datamash

datamash -t ' ' -g 1 last 2 < purchaselist.txt
  • -t ' '字段分隔符=空格
  • -g 1按字段1分组
  • last 2字段2的最后一个值

顺便说一句,4444不在list.txt中,而是显示在最终输出中,因此我假设不需要list.txt。如果那是错字,则可以使用datamash -t ' ' -g 1 last 2 < purchaselist.txt | grep -f list.txt

此外,如果尚未安装datamash,并且您没有安装软件包的特权,则可以改用awk

awk 'ARGIND==1{a[$0]}ARGIND==2{b[$1]=$2}END{for(i in a)if(i in b)print i,b[i]}' list.txt purchaselist.txt

此命令由三部分组成:ARGIND == 1 ARGIND == 2 END

  • ARGIND == 1表示参数索引1(您可以将其视为argv[1]list.txt
  • a[$0] $ 0表示整行,将其放入字典中
  • b[$1] = $2创建另一个词典,用于存储每个项目($2)的价格($1,第二个字段),以这种方式覆盖现有的值
  • END处理完这两个文件后
  • for (i in a) if (i in b)(如果同时位于file.txtpurchaselist.txt
  • 中)
  • print i,b[i]打印键和值

修改 对于非GNU awk,可以使用

awk 'NR==FNR{a[$0];next}{b[$1]=$2}END{for(i in a)if(i in b)print i,b[i]}' list.txt purchaselist.txt

修改 确定...如果您有多个字段:

tac purchaselist.txt | sort -suk2,2 | grep -f list.txt
  • tac保持最新状态
  • -s稳定排序以保持原始顺序
  • -u-k2,2(第二个字段)采用唯一的记录,即仅保留特定键值的第一条记录
  • -k2,2使用2到2之间的字段作为键
  • grep过滤掉不需要的物品

答案 1 :(得分:2)

$ tac purchaselist.txt | awk 'NR==FNR{a[$1]; next} $2 in a{print; delete a[$2]}' list.txt - | tac
0011 1111 300.00 abcd 10 A 151000 181222 ....
0099 2222 200.00 abcd 10 A 151222 181999 ....
0033 3333 110.00 abcd 10 A 151000 181222 ....

如果这是真实数据中的匹配字段号,请将$ 2更改为$ 18。上面的方法适用于未排序的数据,应该没有任何内存问题,因为它只是将list.txt中的580k小键字符串存储在awk命令的内存中。

答案 2 :(得分:1)

以下内容要求文件在要加入的列上进行排序。对示例进行了排序,因此假设真实文件可以排序或已经排序是合理的。

join -j 1 list.txt purchaselist.txt | tac | rev | uniq -f 1 | rev | tac

我不知道这样做是否会更好,但至少不包含两个级别的嵌套循环。将测试输入修改为在4444文件中包含list.txt后,它将正确产生所需的输出。

1111 300.00
2222 200.00
3333 110.00
4444 800.00

提示:https://unix.stackexchange.com/questions/113898/how-to-merge-two-files-based-on-the-matching-of-two-columns

相关问题