1

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

我应该做什么?

4

3 回答 3

4

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

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

  • 它们是有福的数组引用。
    • 一些包含单个 unblessed arrayrefs 项目,
    • 其他包含零个或多个项目(词位或对象)
  • “Lexemes”是一个数组引用,包含两个位置信息字段,以及索引 2 处的字符串内容。

这些信息可以从语法中提取。

编码:

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"
"" 
"" 
"" 
"" 
"" 
""

注意有一些不是来自我们的源代码的空字符串,它们来自包含文件的某个地方。过滤掉这些可能不是不可能的,但有点不切实际。

于 2013-08-25T13:10:52.403 回答
3

您似乎正在尝试使用以下正则表达式来捕获字符串中的多行:

my $your_regexp = m{
    (
        .*  # anything
        \\* # any number of backslashes
        .*  # anything
        \\* # any number of backslashes
        .*  # anything
        \\* # any number of backslashes
        .*  # anything
    )
}x

但这似乎更像是一种绝望的把握,而不是一个经过深思熟虑的计划。

所以你有两个问题:

  1. 查找双引号 ( ")之间的所有内容
  2. 处理这些引号之间可能有多行的情况

正则表达式可以匹配多行。/s修饰符执行此操作。所以试试:

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

你实际上可能有第三个问题:

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

您可以通过搜索替换来处理此问题:

foreach ( @strings ) {
    $_ =~ s/\\\n//g;
}
于 2013-08-25T10:38:42.207 回答
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

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

于 2013-08-26T09:20:24.063 回答