SED或AWK脚本用于替换多个文本

5
我正在尝试使用sed脚本完成以下操作,但是它花费的时间太长了。看起来我做错了什么。
场景: 我在students.txt中有学生记录(>100万条)。在这个文件中(每行)第一组10个字符是学生ID,接下来的10个字符是联系电话号码,以此类推。
students.txt
10000000019234567890XXX... 10000000029325788532YYY... . . . 10010000008766443367ZZZZ...
我还有另一个文件(encrypted_contact_numbers.txt),其中包含所有电话号码和相应的加密电话号码,如下所示:
encrypted_contact_numbers.txt
Phone_Number, Encrypted_Phone_Number
9234567890, 1122334455 9325788532, 4466742178 . . . 8766443367, 2964267747
我想用encrypted_contact_numbers.txt中相应的加密电话号码替换students.txt中所有联系电话号码(第11-20位)。
预期输出:
10000000011122334455XXX... 10000000024466742178YYY... . . . 10010000002964267747ZZZZ...
我正在使用以下sed脚本执行此操作。它可以正常工作,但速度太慢。
方法1:
while read -r pattern replacement; do   
    sed -i "s/$pattern/$replacement/" students.txt
done < encrypted_contact_numbers.txt

方法二:

sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <encrypted_contact_numbers.txt |
sed -f- students.txt > outfile.txt

有没有快速处理这个大文件的方法?
更新:2018年2月9日
如果电话号码不在指定位置(第10-20列),使用AWK和Perl提供的解决方案效果很好。如果我尝试进行全局替换,处理时间太长了。有没有更好的方法来实现这一点?
students.txt: 更新版本
10000000019234567890XXX...9234567890 10000000029325788532YYY... . . . 10010000008766443367ZZZZ9234567890...

2
编辑问题以包括sed脚本。 - John Gordon
1
除了约翰提到的内容外,请在代码标记中添加您的encrypted_contact_numbers.txt文件。 - RavinderSingh13
1
请使用每行代码/数据/错误消息前面的4个空格或突出文本块并使用编辑框左上角的{}格式工具将其格式化为code/data/output/errorMsgs,以正确格式化内容。有关更多信息,请参见editing-helpformatting。祝你好运。 - shellter
3个回答

5

awk 来帮忙啦!

如果您拥有足够的内存将 phone_map 文件保存在内存中。

awk -F', *' 'NR==FNR{a[$1]=$2; next}
                    {key=substr($0,11,20)}
           key in a {$0=substr($0,1,10) a[key] substr($0,21)}1' phone_map data_file

由于缺少数据文件,因此尚未进行测试。一旦两个文件都被扫描一次,速度应该会加快。


我已经添加了样本数据文件 **(Students.txt)**。 - Dhanabalan
@Dhanabalan,请尝试在您的帖子中一次性附上所有细节,尝试Karakfa和我的代码,然后告诉我们它的运行情况。 - RavinderSingh13
我有什么遗漏吗?第一个操作块中的“next”不会在每一行上触发吗?如果是这样,其他两个块将永远不会运行。应该是“NF>1 {a[$1]=$2]; next}”吗?此外,我认为第三个操作块后面的“1”放错了位置。 - cxw
你当然是对的。"第一个文件"的条件确实缺失了,现在已经添加上了。我之前直接在这里输入而没有先进行测试。 - karakfa
NR==FNR 这个技巧我以前没见过,很不错 - 谢谢!(对于未来的读者,它只有在你所在的行来自命令行上的第一个文件时才为真。) - cxw

2
以下的awk命令可能会对您有所帮助。
awk '
FNR==NR{
  sub(/ +$/,"");
  a[$1]=$2;
  next
}
(substr($0,11,10) in a){
  print substr($0,1,10) a[substr($0,11,10)] substr($0,21)
}
' FS=", " encrypted_contact_number.txt students.txt

输出结果如下。稍后将添加说明。
10000000011122334455XXX...
10000000024466742178YYY...

这个样例可以正常工作。您能否解释一下代码,以便我能够将其应用到我的实际场景中,处理大文件。再次感谢您的帮助! - Dhanabalan
@Dhanabalan,请举个例子,不太清楚。如果这是一个新问题,请在新线程中发布。 - RavinderSingh13
@Dhanabalan,为什么不发一个新的帖子呢?因为在这个帖子中给出的答案是根据您的第一个问题而给出的,更改问题后将不好。许多用户都试图帮助您,最好选择一个正确的答案(在此任何可行的答案中)并打开一个新的帖子,谢谢。 - RavinderSingh13
当然。我已经创建了另一篇帖子https://dev59.com/DVYM5IYBdhLWcg3wjAgR谢谢! - Dhanabalan
抱歉,我不知不觉地将它删除了。我重新标记了一下。 - Dhanabalan
显示剩余4条评论

2

没有Perl答案的问题算什么问题呢? :) 这是从Perl Monks讨论中吸取的各种答案。

编辑源码

根据@Borodin的评论进行了编辑。希望内联注释有助于解释。

#!/usr/bin/env perl

use strict;     # keep out of trouble
use warnings;   # ditto

my %numbers;    # map from real phone number to encrypted phone number

open(my $enc, '<', 'encrypted_contact_numbers.txt') or die("Can't open map file");
while(<$enc>) {
    s{\s+}{}g;                               #remove all whitespace
    my ($regular, $encrypted) = split ',';
    $numbers{$regular} = $encrypted;
}

# Make a regex that will match any of the numbers of interest
my $number_pattern = join '|', map quotemeta, keys %numbers;
$number_pattern = qr{$number_pattern}o;
    # Compile the regex - we no longer need the string representation

while(<>) {     # process each line of the input
    next unless length > 1;     # Skip empty lines (don't need this line if there aren't any in your input file)
    substr($_, 10, 10) =~ s{($number_pattern)}{$numbers{$1}}e;
    # substr: replace only in columns 11--20
    # Replacement (s{}{}e): the 'e' means the replacement text is perl code.
    print;  # output the modified line
}

测试

在Perl v5.22.4上进行了测试。

encrypted_contact_numbers.txt

9234567890, 1122334455
9325788532, 4466742178

students.txt:

aaaaaaaaaa9234567890XXX...
bbbbbbbbbb9325788532YYY...
cccccccccc8766443367ZZZZ...
dddddddddd5432112345Nonexistent phone number

./process.pl students.txt输出结果

aaaaaaaaaa1122334455XXX...
bbbbbbbbbb4466742178YYY...
cccccccccc8766443367ZZZZ...
dddddddddd5432112345Nonexistent phone number

这个更改已经在前两行进行了,但没有在后两行进行,这对于这个输入是正确的。

1
当您发布一个没有使用use strictuse warnings,使用两个参数的open,在die字符串中没有任何信息以及全局文件句柄的答案时,它会减少我们鼓励最佳实践的努力。此外--为什么要在数字字符串上使用quotemeta?--您的$numbers{$1} || $1是无意义的--您只是将正则表达式模式构建成哈希键。您还持有一个超过一百万条记录的哈希表,即使在使用它构建正则表达式之后,您仍然长时间保留它。 - Borodin
1
@Borodin 因为我还在学习,同时也在尝试回馈社区。感谢您的反馈!quotemeta因为我不想假设OP的文件只包含数字。我同意它应该是这样的,但小心总比后悔好。**|| $1**如果我省略这个,那么在匹配的行上会出现Use of uninitialized value within %numbers in substitution iterator。为什么? - cxw
“没有注释会短得多!”你应该省略它们。在你编写的每个程序中教人Perl并不是你的职责。“如果我把这个去掉,我会得到Use of uninitialized value within %numbers in substitution iterator”我无法获得那种行为。此外,你不应该在替换中使用/g修饰符,并且你没有理由避免使用s///m//的标准斜杠分隔符。 - Borodin
@Borodin,是的,我去掉了/g修饰符。 s{}{}只是因为在我的浏览器中,语法高亮器无法正确解析s///。关于评论,OP特别要求另一个回答者“Co[u]ld you please explain the code”。无论是否是我的位置,我认为提前评论以期望类似的请求是不过分的。 - cxw
关于解释代码,我总是在代码之前写一个单独的叙述。而s///分隔符的熟悉程度比突出显示更重要。通常是因为它期望有偶数个斜杠,你可以通过添加注释#/来修复它。请注意,你可以编写s/($number_pattern)/{ $numbers{$1} }e,这可能更适合你,因为它强调了替换是Perl代码。 - Borodin
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接