我经常遍历文件中的行并应用多个正则表达式替换,我有时会犯错误,以至于这些表达式之一永远不会在任何行上匹配。 如何在不使我的代码与检查混乱的情况下找出哪个正则表达式不匹配?是否有任何脚本语言为此提供元编程工具或调试工具? 示例输入:
foo
bar
baz
示例脚本(伪代码(:
for each line of the file:
s/foo/lorem/
s/bazzz/ipsum/ # this never matches on any line and should get reported
编辑:我更喜欢马克托马斯的解决方案,因为我希望逐行读取文件,并在第一场比赛后停止应用替换。下次我应该把我的要求说得更清楚。元编程解决方案将有额外的好处,因为我经常逐行进行更复杂的特定于大小写的处理,尽管我认为考虑到答案的灵感,我可能会自己想出一个 ruby 扩展方法,这样我就可以用gsub_debug!
替换gsub!
进行调试,并在程序完成运行时获得所有不匹配的正则表达式的报告。
在 Ruby 中,gsub!
就地修改字符串,如果未找到模式,则返回nil
:
text = "foo
bar
baz"
replacements = [['foo', 'lorem'], ['bazzz', 'ipsum']]
# or with regexen:
replacements = [[/foo/, 'lorem'], [/bazzz/, 'ipsum']]
replacements.each do |pattern, replacement|
unless text.gsub!(pattern, replacement)
puts "#WARNING: #{pattern} wasn't found"
end
end
puts text
它输出:
WARNING: bazzz wasn't found
lorem
bar
baz
请注意,一个接一个地应用替换可能会导致错误。
下面是一个 Ruby 脚本:
- 从简单的分隔文件中读取替换 项
- 从命令行读取要处理的文件
- 一旦匹配,就停止对行应用替换
- 报告哪些模式不匹配
目前,它打印输出,但可以更改为写入文件。
替代.txt
foo lorem
bazzz ipsum
qux notfound
示例.txt
The foo and bazzz
The foo
The bazzz
and the ugly
subs.rb, 调用:ruby subs.rb example.txt
filename = ARGV[0]
substitutions = File.readlines("substitutions.txt").map(&:split)
used = {}
IO.foreach(filename) do |line|
substitutions.each do |pattern, replacement|
if line.gsub!(pattern, replacement)
used[pattern] = true
break #no more substitutions for this line
end
end
puts line
end
unused = substitutions.map(&:first) - used.keys
unless unused.empty?
puts "Unused patterns:"
puts unused
end
输出:
The lorem and bazzz
The lorem
The ipsum
and the ugly
Unused patterns:
qux
不是真正的元编程,但这里有一个Perl版本,它计算每个模式匹配多少行。 它不会修改输入数据或模式,一次只在内存中保留一行输入:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
my @patterns = qw( foo bazzz );
my %matches;
for my $line (<DATA>) {
for my $pat (@patterns) {
if ($line =~ /$pat/) {
$matches{$pat}++;
}
}
}
for my $pat (sort @patterns) {
say "$pat matched no lines" unless $matches{$pat};
}
__DATA__
foo
bar
baz
输出:
bazzz matched no lines
编辑:我多么粗心。 你想做换人,而不是比赛!这实际上使它更简单一些,因为Perl正则表达式替换运算符返回执行的替换次数。 这是一个修改版本,可以做到这一点:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
my %patterns = ( foo => 'lorem', bazzz => 'ipsum' );
my %matches;
for my $line (<DATA>) {
for my $from (keys %patterns) {
my $to = $patterns{$from};
$matches{$from} += $line =~ s/$from/$to/g;
}
}
for my $pat (sort keys %patterns) {
say "$pat matched no lines" unless $matches{$pat};
}
__DATA__
foo
bar
baz
输出
bazzz matched no lines
您所需要的只是:
awk '
BEGIN {
map["foo"] = "lorem"
map["bazzz"] = "ipsum"
}
{
for (re in map) {
cnt[re] += gsub(re,map[re])
}
print
}
END {
for (re in map) {
print re, cnt[re]+0 | "cat>&2"
}
}
' file
以上内容将打印出每次替换的次数 - 按摩以适应,例如:
END {
for (re in map) {
if ( cnt[re] == 0 ) {
print "WARNING: never matched", re | "cat>&2"
}
}
}
它一次只在内存中保留一行文件。