我正在研究perl中的正则表达式。
我想编写一个接受C源代码文件并查找字符串的脚本。
这是我的代码:
my $file1= @ARGV;
open my $fh1, '<', $file1;
while(<>)
{
@words = split(/\s/, $_);
$newMsg = join '', @words;
push @strings,($newMsg =~ m/"(.*\\*.*\\*.*\\*.*)"/) if($newMsg=~/".*\\*.*\\*.*\\*.*"/);
print Dumper(\@strings);
foreach(@strings)
{
print"strings: $_\n";
}
但我在匹配多个字符串时遇到问题
const char *text2 =
"Here, on the other hand, I've gone crazy\
and really let the literal span several lines\
without bothering with quoting each line's\
content. This works, but you can't indent";
我必须做什么?
答案 0 :(得分:4)
这是一个有趣的解决方案。它使用MarpaX::Languages::C::AST
,一个实验性的C解析器。我们可以使用模块附带的c2ast.pl
程序将一段C源文件转换为抽象语法树,我们将其转储到某个文件(使用Data :: Dumper)。然后我们可以用一些魔法提取所有字符串。
不幸的是,AST对象没有方法,但由于它们是自动生成的,我们知道它们在内部的外观。
可以从grammar中提取此信息。
代码:
use strict; use warnings;
use Scalar::Util 'blessed';
use feature 'say';
our $VAR1;
require "test.dump"; # populates $VAR1
my @strings = map extract_value($_), find_strings($$VAR1);
say for @strings;
sub find_strings {
my $ast = shift;
return $ast if $ast->isa("C::AST::string");
return map find_strings($_), map flatten($_), @$ast;
}
sub flatten {
my $thing = shift;
return $thing if blessed($thing);
return map flatten($_), @$thing if ref($thing) eq "ARRAY";
return (); # we are not interested in other references, or unblessed data
}
sub extract_value {
my $string = shift;
return unless blessed($string->[0]);
return unless $string->[0]->isa("C::AST::stringLiteral");
return $string->[0][0][2];
}
从递归到迭代重写find_strings
:
sub find_strings {
my @unvisited = @_;
my @found;
while (my $ast = shift @unvisited) {
if ($ast->isa("C::AST::string")) {
push @found, $ast;
} else {
push @unvisited, map flatten($_), @$ast;
}
}
return @found;
}
测试C代码:
/* A "comment" */
#include <stdio.h>
static const char *text2 =
"Here, on the other hand, I've gone crazy\
and really let the literal span several lines\
without bothering with quoting each line's\
content. This works, but you can't indent";
int main() {
printf("Hello %s:\n%s\n", "World", text2);
return 0;
}
我运行了命令
$ perl $(which c2ast.pl) test.c -dump >test.dump;
$ perl find-strings.pl
产生了输出
"Here, on the other hand, I've gone crazyand really let the literal span several lineswithout bothering with quoting each line'scontent. This works, but you can't indent"
"World"
"Hello %s\n"
""
""
""
""
""
""
请注意,我们的源代码中有一些空字符串,它们来自包含的文件。过滤掉那些可能并非不可能,但有点不切实际。
答案 1 :(得分:3)
您似乎正在尝试使用以下正则表达式捕获字符串中的多行:
my $your_regexp = m{
(
.* # anything
\\* # any number of backslashes
.* # anything
\\* # any number of backslashes
.* # anything
\\* # any number of backslashes
.* # anything
)
}x
但它似乎更多地是对绝望的把握,而不是刻意考虑的计划。
所以你有两个问题:
"
)正则表达式可以匹配多行。 /s
修饰符执行此操作。所以试试:
my $your_new_regexp = m{
\" # opening quote mark
([^\"]+) # anything that's not a quote mark, capture
\" # closing quote mark
}xs;
你可能确实遇到了第3个问题:
你可以通过搜索替换来处理这个问题:
foreach ( @strings ) {
$_ =~ s/\\\n//g;
}
答案 2 :(得分:1)
这是一种提取源文件中所有字符串的简单方法。我们可以做出一个重要的决定:我们是否预先处理代码?如果没有,如果它们是通过宏生成的,我们可能会遗漏一些字符串。我们还必须将#
视为评论字符。
由于这是一个快速而肮脏的解决方案,因此C代码的语法正确性不是问题。然而,我们将尊重评论。
现在,如果源已经过预处理(使用gcc -E source.c
),那么多行字符串已经折叠成一行!此外,评论已被删除。甜。剩下的唯一注释是提及行号和源文件以进行调试。基本上我们所要做的就是
$ gcc -E source.c | perl -nE'
next if /^#/; # skip line directives etc.
say $1 while /(" (?:[^"\\]+ | \\.)* ")/xg;
'
输出(将我的其他答案中的测试文件作为输入):
""
"__isoc99_fscanf"
""
"__isoc99_scanf"
""
"__isoc99_sscanf"
""
"__isoc99_vfscanf"
""
"__isoc99_vscanf"
""
"__isoc99_vsscanf"
"Here, on the other hand, I've gone crazyand really let the literal span several lineswithout bothering with quoting each line'scontent. This works, but you can't indent"
"Hello %s:\n%s\n"
"World"
所以是的,这里有很多垃圾(它们似乎来自__asm__
块),但这种效果非常好。
请注意我使用的正则表达式:/(" (?:[^"\\]+ | \\.)* ")/x
。捕获中的模式可以解释为
" # a literal '"'
(?: # the begin of a non-capturing group
[^"\\]+ # a character class that matches anything but '"' or '\', repeated once or more
|
\\. # an escape sequence like '\n', '\"', '\\' ...
)* # zero or more times
" # closing '"'
此解决方案有哪些限制?
gcc
clang
也支持-E
选项,但我不知道输出是如何格式化的。myfunc('"', a_variable, '"')
将被提取为"', a_variable, '"
。哦等等,我们可以通过解析预处理器插入的源文件注释来修复最后一位。他们看起来像
# 29 "/usr/include/stdio.h" 2 3 4
因此,如果我们记住当前文件名,并将其与我们想要的文件名进行比较,我们可以跳过不需要的字符串。这一次,我会把它写成一个完整的脚本,而不是一个单行。
use strict; use warnings;
use autodie; # automatic error handling
use feature 'say';
my $source = shift @ARGV;
my $string_re = qr/" (?:[^"\\]+ | \\.)* "/x;
# open a pipe from the preprocessor
open my $preprocessed, "-|", "gcc", "-E", $source;
my $file;
while (<$preprocessed>) {
$file = $1 if /^\# \s+ \d+ \s+ ($string_re)/x;
next if /^#/;
next if $file ne qq("$source");
say $1 while /($string_re)/xg;
}
用法:$perl extract-strings.pl source.c
现在产生输出:
"Here, on the other hand, I've gone crazyand really let the literal span several lineswithout bothering with quoting each line'scontent. This works, but you can't indent"
"Hello %s:\n%s\n"
"World"
如果你不能使用方便的预处理器来折叠多行字符串并删除注释,这会变得更加丑陋,因为我们必须自己考虑所有这些。基本上,您希望立即在整个文件中啜饮,而不是逐行迭代。然后,您跳过任何评论。不要忘记忽略预处理程序指令。之后,我们可以像往常一样提取字符串。基本上,你必须重写语法
Start → Comment Start
Start → String Start
Start → Whatever Start
Start → End
到正则表达式。由于以上是常规语言,这不是太难。