Perl正则表达式重复匹配

时间:2021-04-14 13:56:26

标签: regex perl

我在使用“?”时遇到了奇怪的行为正则表达式重复。我正在处理日志文件,在其中搜索特定的 HTTP 错误响应,例如。 401. 该行可能包含但可能不包含响应正文。所以我想匹配这两种情况。 我有以下代码。

#!/usr/bin/perl
$match = 'response 401';
$line = '2021-04-08 07:15:01 |  INFO | [http-nio-8080-exec-11] | rId:123456789 | ip:127.0.0.1 | activationId: abcdefg | user: admin | response 401: headers: Cache-Control: [no-cache, no-store, max-age=0, must-revalidate] / Content-Length: [60] / Content-Type: [application/json;charset=UTF-8] / Date: [Thu, 08 Apr 2021 05:15:01 GMT] / Expires: [0] / Pragma: [no-cache] | body: {"errors":[{"message":"Bad credentials","repeatable":true}]}';
    
my($tstamp, $level, $thread, $body) = $line =~ m/^(.*?)\s+\|\s+(\w+)\s+\|\s+\[(.*?)\].*?$match.*?(?:body\:\s+({.*}))?/;
if($body) {
  print "body: $body\n";
}

这不会打印任何内容。我希望它应该是 .*?$match.*? 应该匹配行的最小部分并为 body 模式留下足够的空间。但显然不会。 当我更改正则表达式并从 ? 模式中删除 body 并将其设为强制行匹配时。

my($tstamp, $level, $thread, $body) = $line =~ m/^(.*?)\s+\|\s+(\w+)\s+\|\s+\[(.*?)\].*?$match.*?(?:body\:\s+({.*}))/;

但这不会匹配没有 body 的行。 正则表达式有什么问题?我怀疑 .*? 模式之前的非贪婪 (?:body...)? 模式会吃掉输入,因为它可以与可选正文一起使用。

如何编写正确的正则表达式?

2 个答案:

答案 0 :(得分:6)

根据您展示的样品,您可以尝试以下操作吗?

^(\d{4}-\d{2}-\d{2}\s*(?:\d{2}:){2}\d{2})\s+\|\s+(\S+)\s+\|\s+\[([^]]*)\].*?(body.*)?$

Here is online demo for above regex

说明:为以上添加详细说明。

^                                          ##Matching starting of value by caret sign.
(\d{4}-\d{2}-\d{2}\s*(?:\d{2}:){2}\d{2})   ##Creating 1st capturing group to match time stamp here.
\s+\|\s+                                   ##Matching spaces pipe spaces(one or more occurrences).
(\S+)                                      ##Creating 2nd capturing group which has everything apart from space, which will have INFO/WARN/ERROR etc here.
\s+\|\s+\[                                 ##Matching spaces pipe spaces(one or more occurrences).
([^]]*)                                    ##Creating 3rd capturing group which has everything till ] occurrence in it.
\].*?                                      ##Matching ] with lazy match.
(body.*)?$                                 ##Creating 4th capturing group which will match from body to till end of line and keeping it optional at the end of the line/value.

答案 1 :(得分:5)

您可以在组 4 中使用可选部分并断言字符串的结尾。

^(.*?)\s+\|\s+(\w+)\s+\|\s+\[([^][]*)\].*?(?:\s+\|\s+body:\s+({.*}))?$
  • ^ 字符串开头
  • (.*?)\s+\| Capture group 1 尽可能匹配任何字符并匹配空格和|
  • \s+(\w+)\s+\| 匹配空格并捕获 group 2 中的 1+ 个单词字符并匹配空格和 |
  • \s+\[([^][]*)\] 匹配空格并捕获组 3
  • [...] 之间的所有内容
  • .*? 尽可能匹配任何字符
  • (?:\s+\|\s+body:\h+({.*}))? 可选匹配空格之间的 |body: 并捕获 group 4
  • {...} 之间的所有内容
  • $ 字符串结束

Regex demo

使用示例代码:

$match = 'response 401';
$line = '2021-04-08 07:15:01 |  INFO | [http-nio-8080-exec-11] | rId:123456789 | ip:127.0.0.1 | activationId: abcdefg | user: admin | response 401: headers: Cache-Control: [no-cache, no-store, max-age=0, must-revalidate] / Content-Length: [60] / Content-Type: [application/json;charset=UTF-8] / Date: [Thu, 08 Apr 2021 05:15:01 GMT] / Expires: [0] / Pragma: [no-cache] | body: {"errors":[{"message":"Bad credentials","repeatable":true}]}';

my($tstamp, $level, $thread, $body) = $line =~ m/^(.*?)\s+\|\s+(\w+)\s+\|\s+\[([^][]*)\].*?(?:\s+\|\s+body:\s+({.*}))?$/;
if($body) {
  print "body: $body\n";
}

输出

body: {"errors":[{"message":"Bad credentials","repeatable":true}]}

如果没有body,仍然可以得到$tstamp$level$thread的值