【问题标题】:Perl regex for a character NOT within string characters不在字符串字符内的字符的 Perl 正则表达式
【发布时间】:2020-11-28 23:43:31
【问题描述】:

我正在编写一个“编译”shell 代码的 perl 脚本。我需要做的一件事是检测 ; 字符并处理它们(例如一行上的多个命令),但前提是它们没有被转义(通过 \ )或在字符串中。例如,我们不应该匹配 'some ; text ;' ,但我们应该匹配 echo ";ignore; inside ;" ; echo 'something;' \; 'else'; 中两个 echo 语句之间的分号

在上面的例子中,正好两个分号应该匹配。

我已经尝试过使用正则表达式循环

while ($_ =~ /('[^']+')*?("[^"]+")*?(?<!\\)(?<match>;)/g) 
  { 
    print "semiolon: $+{match}\n"; 
    # process the match . . . 
  }

虽然这适用于某些示例,但在某些情况下它无法正确检测分号在两个字符串“内部”;因为它在当前比赛之前无法匹配其中的一对。我将如何确保我们只匹配字符串之外的分号?

提前致谢。

【问题讨论】:

  • 嗯,这根本不是您编写解析器的方式。你应该做一些阅读,然后我们像Marpa::R2
  • 单引号可以出现在双引号内吗?例如"Jo's place"
  • 抱歉,正如您所发现的,正则表达式不是解析此类输入的正确工具。我个人会设置一个简单的有限状态机解析器。学习 FSM 解析可能值得您花时间。
  • 您还可以在 shell 代码中包含 HERE 文档,而不仅仅是单引号和双引号字符串。
  • 你看到metacpan.org/pod/Shell::Parser了吗?此外,metacpan.org/pod/Shell::Parser 声称还包含一个用于 shell 代码的解析器。

标签: regex string perl compiler-construction


【解决方案1】:

我同意其他评论者的观点,即有更好的方法来开发这样的解析器。

不过,我想提出两个建议:

while(/\G((?:[^;'"\\]++|'[^']*+'|"[^"]*+"|\\.)*;)/gx){ 
    print "  command: $1\n"; 
    # process the match . . . 
}
  • \G 是一个零宽度断言,它与前一个 m//g 停止的位置相匹配,请参阅 perlop#\G-assertion。 (在文档中还有一个可能感兴趣的类似 lex 的扫描器的示例。)
  • 非捕获组包含无害字符、带引号的字符串和转义字符
  • 请注意使用 possessive quantifiers 以避免由于回溯导致的性能问题。
  • 我删除了否定断言(?&lt;!\\),因为这在echo \\; 等情况下会失败

此代码适用于您给定的示例。但是,例如bash 允许转义双引号字符串内的双引号,例如echo "\""。 如果您的 shell 也应该接受这样的代码,则必须扩展正则表达式:

while(/\G( # anchor for beginning
    (?:[^;'"\\]++            # harmless chars
    |'[^']*+'                # or single-quoted string
    |"(?:                    # or double-quoted string,
        [^"\\]++               #   containing harmless chars
        |\\.                   #   or an escaped char
        )*+"                   #   with arbitrary many repetitions
    |\\.                     # or an escaped char
    )*+                      # with arbitrary many repetitions
    ;)                       # end with semi-colon
/gx){ 
    print "  command: $1\n"; 
    # process the match . . . 
}

这种纯正则表达式解决方案非常容易出错。而且,您发现必须处理的异常越多,模式就越复杂,调试代码就越困难。

一些测试:

use strict;
use warnings;
use Test::More tests => 16;

my $samples = [
    {"'some ; text ;'" => []},
    {'echo;' => ['echo;']},
    {'echo ";ignore; inside ;" ; echo \'something;\' \; \'else\';' => [
            'echo ";ignore; inside ;" ;', ' echo \'something;\' \; \'else\';']},
    {'echo moep; echo moep;' => [ 'echo moep;', ' echo moep;']},
    {'echo \a ; echo moep;' => [ 'echo \a ;', ' echo moep;']},
    {'echo \\a ; echo moep;' => [ 'echo \\a ;', ' echo moep;']},
    {'echo \\\a ; echo moep;' => [ 'echo \\\a ;', ' echo moep;']},
    {'echo \; echo moep;' => [ 'echo \; echo moep;']},
    {'echo \\; echo moep;' => [ 'echo \; echo moep;']}, # '\\;' eq '\;' !
    {'echo \\\; echo moep;' => [ 'echo \\\;', ' echo moep;']},
    {'echo ";\';\';"; echo moep;' => [ 'echo ";\';\';";', ' echo moep;']},
    {'echo "\";"; echo moep;' => [ 'echo "\";";', ' echo moep;']},
    {'echo ";\""; echo moep;' => [ 'echo ";\"";', ' echo moep;']},
    {'echo "\";\""; echo moep;' => [ 'echo "\";\"";', ' echo moep;']},
    {'echo ";\\\\"; echo moep;' => [ 'echo ";\\\\";', ' echo moep;']},
    {'echo "\\\\\";\""; echo moep;' => [ 'echo "\\\\\";\"";', ' echo moep;']},
];

for my $sample(@$samples){
    while(my ($line, $test) = each %$sample){
        my @result = $line =~ /\G((?:[^;'"\\]++|'[^']*+'|"(?:[^"\\]++|\\.)*+"|\\.)*+;)/g;
        is_deeply(\@result, $test, $line);
    }
}

不过,您仍然可以轻松找到许多误报/误报样本。例如,我没有处理括号。使用recursive subpatterns 会使上述模式变得更加复杂。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 2019-11-09
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    相关资源
    最近更新 更多