通过在perl中使用regex在源代码中查找字符串

时间:2013-08-25 10:28:25

标签: regex string perl

我正在研究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"; 

我必须做什么?

3 个答案:

答案 0 :(得分:4)

这是一个有趣的解决方案。它使用MarpaX::Languages::C::AST,一个实验性的C解析器。我们可以使用模块附带的c2ast.pl程序将一段C源文件转换为抽象语法树,我们将其转储到某个文件(使用Data :: Dumper)。然后我们可以用一些魔法提取所有字符串。

不幸的是,AST对象没有方法,但由于它们是自动生成的,我们知道它们在内部的外观。

  • 他们是受祝福的arrayrefs。
    • 有些包含一个未完成的项目内容,
    • 其他包含零个或多个项目(词汇或对象)
  • “Lexemes”是一个带有两个位置信息字段的arrayref,以及索引为2的字符串内容。

可以从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

但它似乎更多地是对绝望的把握,而不是刻意考虑的计划。

所以你有两个问题:

  1. 在双引号("
  2. 之间找到所有内容
  3. 处理这些引号之间可能有多行的情况
  4. 正则表达式可以匹配多行。 /s修饰符执行此操作。所以试试:

    my $your_new_regexp = m{
        \"       # opening quote mark
        ([^\"]+) # anything that's not a quote mark, capture
        \"       # closing quote mark
    }xs;
    

    你可能确实遇到了第3个问题:

    1. 从字符串
    2. 中删除尾部反斜杠/换行符对

      你可以通过搜索替换来处理这个问题:

      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

到正则表达式。由于以上是常规语言,这不是太难。