【问题标题】:Finding and replacing from a large file从大文件中查找和替换
【发布时间】:2017-01-10 06:59:10
【问题描述】:

屏蔽问题:我需要从单个大文本文件(input.txt,100+ MB)中定位和屏蔽(即替换为“XXX”)某些术语(单词/表达式)。我需要查找的术语 (10K+) 保存在单个文件 (to_mask.txt) 中。我怎样才能有效地执行此操作?

我想分两步做:首先找到实际包含术语的行

grep -Ff to_mask.txt -o -n input.txt

接下来检查输出并进行实际替换(术语->“XXX”)。

这似乎有点乏味,能不能用更聪明的方式来做?

欢迎任意组合基本命令(grep、sed、awk、one-line-perl)!

更新:

MarcoS、Kenavoz、Ed Morton 和 Sobrique 都提供了可行的解决方案,谢谢! 我选择 Sobrique 的解决方案作为我接受的解决方案,因为考虑到我的数据,它比其他解决方案快得多。它可能无法处理一些特殊情况,但我确信它可以扩展到能够这样做,而且它可以在当前条件下完成手头的工作。

更新 2:

作为参考,以下是 Kenavoz 提供的解决方案:

sed -f <(sed 's~^~s\~~;s~$~\~XXX\~~' to_mask.txt) input.txt

【问题讨论】:

  • 我认为您应该首先熟悉Scunthorpe Problem 的概念,然后用不止一个班轮来解决这个问题(尤其是在涉及其他最终用户的情况下)。此外,口罩的过滤通常使用 0 b 5 C u r 1 + y 进行环绕,计算机难以发现,而人类往往能够阅读。
  • 感谢@Draw Sloan,我对垃圾邮件问题有点熟悉。但是,我需要用尽可能少的库来实现这一点(它将在客户端运行),所以为了简单起见,我可能会失去一些效率。

标签: bash perl awk sed grep


【解决方案1】:

我想我会这样处理它:

#!/usr/bin/perl
use strict;
use warnings;

#read the mask file into memory. 
open ( my $mask, '<', "to_mask.txt" ) or die $!;
chomp ( my @terms = <$mask> ); 
close ( $mask );
#build a really big regex
#map quotemeta means handling metachars. 
#if you _know_ there are none, then you can omit this.
#or if you actually want to be able to use regex in your terms file. 
my $mega_regex = join "|", map { quotemeta } @terms; 

   #compile it into a non-capturing regex, and use \b to anchor on word boundaries. 
   #You don't want to be filtering out Scunthorpe ... 
   $mega_regex = qr/\b(?:$mega_regex)\b/;

#<> means iterate 'stdin' or 'files specified on command line'. 
#just like how grep/sed/awk does it
while ( <> ) { 
    s/$mega_regex/XXX/g;
    print;  #to STDOUT
}

【讨论】:

  • 谢谢。非捕获正则表达式似乎不起作用,但如果我将其注释掉,它就像一个魅力。顺便说一句,如果我的掩码术语是“a”、“b”和“c”,这对于非捕获 $mega_regex: (?^:\b(?:a|b|c)\b) 是否正确
  • Hrm,不确定哪个位不起作用 - 但是,(?:pattern) 是一个非捕获组。在这种情况下,它并不重要,但它是多余的。我得到了合理的结果(数据集小得多):(?^:\b(?:fish|foo|moo)\b)
  • 也不确定,语法似乎没问题。但正如你所说,那部分是多余的,所以你的解决方案完美无缺。
【解决方案2】:

你可以试试:

while read mask; do sed -i "s/$mask/XXX/g" input.txt; done < to_mask.txt

可能不是世界上最有效的解决方案,但它应该可以胜任... :-)
作为奖励,它只使用 shell 和 sed 命令...

更新

这是一个更快的解决方案(它只写入一次大的input.txt 文件)。 它首先构建一个fullmask 变量,作为由|OR 运算符)分隔的所有掩码的串联。 不过,我发现它比发布的第一个解决方案不太清楚...... :-)

fullmask=""; cat to_mask.txt | while read mask; do fullmask="$fullmask|$mask"; done && sed -i "s/$fullmask/XXX/g" input.txt

请注意,我没有测试过这个解决方案,它可能包含一些问题...
此外,它假设to_mask.txt 不包含任何| 或任何/ 字符...

更新 2

对不起! sed 不支持正则表达式,替换... :-(
我使用perl 提出了这个解决方案,更丑陋,但绝对有效(只是在一个简单的用例上测试过):

 fullmask="("; while read mask; do if [ "$fullmask" != "(" ]; then fullmask="$fullmask|$mask"; else fullmask="$fullmask$mask"; fi; done < to_mask.txt; fullmask="$fullmask)"; perl -p -i -e "s/$fullmask/XXX/g" input.txt

【讨论】:

  • 只要 to_mask.txt 文件不包含带有需要反斜杠的特殊字符的行,就可以正常工作......如果是这样,那么 to_mask.txt 可以事先进行操作,以便它可以像上面那样使用。
  • @louigi600:你是对的...OP 可以更改分隔符,以避免/ 问题...但这只能通过知道to_mask.txt 文件的内容并使用不存在的字符(如果有的话...)。
  • @Sobrique:你也是对的......呃...... :-) 查看我的更新答案,刚刚发布了一个一次性解决方案......
  • 感谢 @louigi600 的努力,但正如 Sobrique 指出的那样,重点是效率,然后是简单性。我也在考虑遍历屏蔽文件,它确实执行了这项工作。但是由于 grep 已经非常有效地实现了 Commentz-Walter 算法,我不想重新发明轮子。
  • @MarcoS:分隔符不是问题,我可以控制掩蔽术语,不会有什么时髦的东西
【解决方案3】:

您只需要 1 个命令:

awk 'NR==FNR{t=(t?t"|":"")$0;next} {gsub(t,"XXX")} 1' to_mask.txt input.txt

【讨论】:

  • 感谢您在上面的评论!
猜你喜欢
  • 2011-02-16
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 2018-02-14
  • 1970-01-01
  • 2011-10-20
  • 2011-07-18
相关资源
最近更新 更多