【问题标题】:Perl optional capture groups not working?Perl 可选捕获组不起作用?
【发布时间】:2021-12-01 04:39:20
【问题描述】:

我有以下 sample.txt 文件:

2021-10-07 10:32:05,767 ERROR [LAWT2] blah.blah.blah - Message processing FAILED: <ExecutionReport blah="xxx" foo="yyy" SessionID="kkk" MoreStuff="zz"> Total time for which application threads were stopped: 0.0003858 seconds, Stopping threads took: 0.0000653 seconds
2021-10-07 10:31:32,902 ERROR [LAWT6] blah.blah.blah - Message processing FAILED: <NewOrderSingle SessionID="zkx" TargetSubID="ttt" Account="blah" MsgType="D" BookingTypeOverride="0" Symbol="6316" OtherField1="othervalue1" Otherfield2="othervalue2"/></D></NewOrderSingle>

我只想获取两个关键字段:“SessionID”和“MsgType”并像这样打印:

SessionID="kkk"|
SessionID="zkx"|MsgType="D"

换句话说:如果组匹配不存在,我只想打印空白。

我尝试了以下方法,但没有成功:

$$ perl -ne '/ (SessionID=".*?")? .*(MsgType=".*?")? / and print "$1|$2\n"' sample.txt
SessionID="kkk"|
SessionID="zkx"|

有人可以在这里启发我吗?非常感谢。

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    这并不像看起来那么容易:

    / (SessionID=".*?")? .*(MsgType=".*?")? /
                         ~~
    

    下划线部分与MsgType 匹配,即使它存在,即使您将? 添加到它。引擎会尝试从左边开始匹配最长的部分,所以如果它可以通过尽快匹配成功,它不会返回 MsgType。

    但可以使用环视断言:

    / (SessionID="[^"]*")? (?:(?!.*?MsgType)|.*? (MsgType=".*?")).* /
    

    即要么 SessionID 后面没有 MsgType,要么它就在那里,我们捕获了它。

    我不建议在捕获组上使用量词。另外,看起来日志中包含 XML,那么提取它并使用解析器呢?

    【讨论】:

    • 天哪,我正计划在这个正则表达式上放置多个字段。有没有更简单的方法来做到这一点?不,这不是 xml,这实际上是日志文件中一长串行的一部分,所以我不能使用任何 XML 解析器,很遗憾......但是谢谢你的回答,非常聪明。跨度>
    • 更简单?使用多个正则表达式 ;-)
    • @ArthurAccioly 不一定“更容易”,但从长远来看,最灵活/最可靠的方法是为任何东西编写一个小型解析器,即使它不是 XML。有很多模块可以帮助您做到这一点,或者您可以构建一堆 m//gc 正则表达式匹配和一些逻辑......但如果您以前从未做过,则需要一些练习。
    【解决方案2】:

    你可以使用

    perl -ne '/\h(SessionID="[^"]*")?(?:\h++.*(MsgType="[^"]*"))?\h/ and print "$1|$2\n"' 
    

    请参阅regex demo详情

    • \h - 水平空格
    • (SessionID="[^"]*")? - 第 1 组:可选的 SessionID=",除 " 之外的任何零个或多个字符,然后是 "
    • (?:\h++.*(MsgType=".*?"))? - 一个可选的(但贪婪的)序列
      • \h++ - 一个或多个水平空格
      • .* - 除换行符以外的任何零个或多个字符尽可能多
      • (MsgType="[^"]*") - 第 2 组:SessionID=",除 " 之外的任何零个或多个字符,然后是 "
    • \h - 水平空格。

    online demo

    s='2021-10-07 10:32:05,767 ERROR [LAWT2] blah.blah.blah - Message processing FAILED: <ExecutionReport blah="xxx" foo="yyy" SessionID="kkk" MoreStuff="zz"> Total time for which application threads were stopped: 0.0003858 seconds, Stopping threads took: 0.0000653 seconds
    2021-10-07 10:31:32,902 ERROR [LAWT6] blah.blah.blah - Message processing FAILED: <NewOrderSingle SessionID="zkx" TargetSubID="ttt" Account="blah" MsgType="D" BookingTypeOverride="0" Symbol="6316" OtherField1="othervalue1" Otherfield2="othervalue2"/></D></NewOrderSingle>'
    perl -ne '/\h(SessionID=".*?")?(?:\h++.*(MsgType=".*?"))?\h/ and print "$1|$2\n"' <<< "$s"
    

    打印出来:

    SessionID="kkk"|
    SessionID="zkx"|MsgType="D"
    

    【讨论】:

      【解决方案3】:

      抱歉,我在问题中没有提到的一点是,我打算提取多个字段并按确定的顺序打印它们,所以我最终改写了一个 awk 脚本。

      我把它放在这里以防其他人想要使用(我正在处理日志文件中的数千行,所以脚本是一个不错的选择)。

      #!/usr/bin/awk
      function get_field(the_array, the_field, the_line){
        for (key in the_array) {
            if (the_array[key] ~ the_field){
                if (the_line == "")
                    the_line = the_array[key]
                else
                    the_line = the_line "|" the_array[key]
                break
            }
        }
        return the_line
      }
      BEGIN{
          the_line = ""
      }
      {
          the_line = ""
          delete the_keys
          for(f=1;f<=NF;f++){
              if (($f ~ "^(ClOrdID|Symbol|MsgType|SessionID|OrdStatus)=") && (the_keys[$f] == "")){
                  if (the_line == "")
                      the_line = $f
                  else
                      the_line = $f"|"the_line
                  the_keys[$f]++
              }
          }
          arr[the_line]++
      }
      END{
          for(i in arr) {
              if (i ~ "|"){
                  the_line = ""
                  split(i,aa,"|")
                  # Print the fields in the correct order
                  the_line = get_field(aa,"SessionID",the_line)
                  the_line = get_field(aa,"ClOrdID",the_line)
                  the_line = get_field(aa,"MsgType",the_line)
                  the_line = get_field(aa,"OrdStatus",the_line)
                  the_line = get_field(aa,"Symbol",the_line)
                  print the_line
              } else {
                  print(i)
              }
          }
      }
      

      使用它:

      $$ awk -f aa.awk sample.txt
      SessionID="kkk"
      SessionID="zkx"|MsgType="D"|Symbol="6316"
      

      【讨论】:

      • 干得好。如果您想临时提取特定值,我也使用sed,例如sessionID=$(sed -n 's/.*SessionID="\([^"]*\).*/\1/p' file)
      猜你喜欢
      • 2022-08-21
      • 2012-09-22
      • 2017-08-03
      • 1970-01-01
      • 2011-10-12
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      相关资源
      最近更新 更多