awk 模式可以匹配多行吗?

Posted

技术标签:

【中文标题】awk 模式可以匹配多行吗?【英文标题】:Can awk patterns match multiple lines? 【发布时间】:2012-12-30 07:49:33 【问题描述】:

我有一些复杂的日志文件,我需要编写一些工具来处理它们。我一直在玩 awk,但我不确定 awk 是否适合做这个。

我的日志文件是 OSPF 协议解码的打印输出,其中包含各种协议 pkts 及其内容的文本日志,以及用它们的值标识的各种协议字段。我想处理这些文件并仅打印出与特定 pkts 相关的某些日志行。每个 pkt 日志可以由该 pkt 条目的不同行数组成。

awk 似乎能够处理与模式匹配的单行。我可以找到所需的 pkt,但随后我需要匹配后面几行中的模式以确定它是否是我要打印的 pkt。

查看此问题的另一种方法是,我希望隔离日志文件中的几行,并根据多行上的模式匹配打印出作为特定 pkt 详细信息的那些行。

由于 awk 似乎是基于行的,我不确定这是否是最好的工具。

如果awk可以做到这一点,它是如何做到的?如果没有,关于使用哪种工具的任何建议?

【问题讨论】:

你能放一些示例日志吗? awk 是面向记录的,而不是面向行的,所以它几乎肯定是适合这项工作的工具。如果您发布一些示例输入和所需的输出,我们可以向您展示如何在 awk 中执行此操作。 感谢大家的回复——他们都很有帮助。 【参考方案1】:

awk 能够处理从开始模式到结束模式

/start-pattern/,/end-pattern/ 
  print

我正在寻找如何匹配

 * Implements hook_entity_info_alter().
 */
function file_test_entity_type_alter(&$entity_types) 

如此创造

/\* Implements hook_/,/function / 
  print

我需要的内容。一个更复杂的例子是跳过行并擦掉非空格部分。注意 awk 是一个记录(行)和单词(空格分割)的工具。

# start,end pattern match using comma
/ \* Implements hook_(.*?)\./,/function (.\S*?)/ 
  # skip php multi line comment end
  $0 ~ / \*\// skip

  # Only print 3rd word
  if ($0 ~ /Implements/) 
    hook=$3
    # scrub of opening parenthesis and following.
    sub(/\(.*$/, "", hook)
    print hook
  

  # Only print function name without parenthesis
  if ($0 ~ /function/) 
    name=$2

    # scrub of opening parenthesis and following.
    sub(/\(.*$/, "", name)

    print name
    print ""
  

希望这也有帮助。

另见ftp://ftp.gnu.org/old-gnu/Manuals/gawk-3.0.3/html_chapter/gawk_toc.html

【讨论】:

【参考方案2】:

我不时对 sendmail 日志做这种事情。

给定:

Jan 15 22:34:39 mail sm-mta[36383]: r0B8xkuT048547: to=<www@web3>, delay=4+18:34:53, xdelay=00:00:00, mailer=esmtp, pri=21092363, relay=web3., dsn=4.0.0, stat=Deferred: Operation timed out with web3.
Jan 15 22:34:39 mail sm-mta[36383]: r0B8hpoV047895: to=<www@web3>, delay=4+18:49:22, xdelay=00:00:00, mailer=esmtp, pri=21092556, relay=web3., dsn=4.0.0, stat=Deferred: Operation timed out with web3.
Jan 15 22:34:51 mail sm-mta[36719]: r0G3Youh036719: from=<obfTaIX3@nickhearn.com>, size=0, class=0, nrcpts=0, proto=ESMTP, daemon=IPv4, relay=[50.71.152.178]
Jan 15 22:35:04 mail sm-mta[36722]: r0G3Z2SF036722: lost input channel from [190.107.98.82] to IPv4 after rcpt
Jan 15 22:35:04 mail sm-mta[36722]: r0G3Z2SF036722: from=<amahrroc@europe.com>, size=0, class=0, nrcpts=0, proto=SMTP, daemon=IPv4, relay=[190.107.98.82]
Jan 15 22:35:36 mail sm-mta[36728]: r0G3ZXiX036728: lost input channel from ABTS-TN-dynamic-237.104.174.122.airtelbroadband.in [122.174.104.237] (may be forged) to IPv4 after rcpt
Jan 15 22:35:36 mail sm-mta[36728]: r0G3ZXiX036728: from=<clunch.hilarymas@javagame.ru>, size=0, class=0, nrcpts=0, proto=SMTP, daemon=IPv4, relay=ABTS-TN-dynamic-237.104.174.122.airtelbroadband.in [122.174.104.237] (may be forged)

我使用类似这样的脚本:

#!/usr/bin/awk -f

BEGIN 
  search=ARGV[1];  # Grab the first command line option
  delete ARGV[1];  # Delete it so it won't be considered a file


# First, store every line in an array keyed on the Queue ID.
# Obviously, this only works for smallish log segments, as it uses up memory.

  line[$6]=sprintf("%s\n%s", line[$6], $0);


# Next, keep a record of Queue IDs with substrings that match our search string.
index($0, search) 
  show[$6];


# Finally, once we've processed all input data, walk through our array of "found"
# Queue IDs, and print the corresponding records from the storage array.
END 
  for(qid in show) 
    print line[qid];
  

得到以下输出:

$ mqsearch airtel /var/log/maillog

Jan 15 22:35:36 mail sm-mta[36728]: r0G3ZXiX036728: lost input channel from ABTS-TN-dynamic-237.104.174.122.airtelbroadband.in [122.174.104.237] (may be forged) to IPv4 after rcpt
Jan 15 22:35:36 mail sm-mta[36728]: r0G3ZXiX036728: from=<clunch.hilarymas@javagame.ru>, size=0, class=0, nrcpts=0, proto=SMTP, daemon=IPv4, relay=ABTS-TN-dynamic-237.104.174.122.airtelbroadband.in [122.174.104.237] (may be forged)

这里的想法是我正在打印与我要搜索的字符串的 Sendmail 队列 ID 匹配的所有行。代码的结构当然是日志文件结构的产物,因此您需要针对要分析和提取的数据自定义解决方案。

【讨论】:

【参考方案3】:

Awk 确实是基于记录的。默认情况下,它将一行视为一条记录,但您可以使用 RS(记录分隔符)变量对其进行更改。

解决此问题的一种方法是使用 sed 进行第一次遍历(如果您愿意,也可以使用 awk 执行此操作),以使用不同的字符(如换页)分隔记录。然后你可以编写你的 awk 脚本,它将这组行视为一条记录。

例如,如果这是您的数据:

animal 0
name: joe
type: dog
animal 1
name: bill
type: cat
animal 2
name: ed
type: cat

用换页符分隔记录:

$ cat data | sed $'s|^\(animal.*\)|\f\\1|'

现在我们将把它传递给 awk。下面是一个有条件地打印记录的例子:

$ cat data | sed $'s|^\(animal.*\)|\f\\1|' | awk '
      BEGIN  RS="\f"                                      
      /type: cat/  print '

输出:

animal 1
name: bill
type: cat

animal 2
name: ed
type: cat

编辑:作为奖励,这里是使用 awk-ward ruby​​ 的方法(-014 表示使用换页符(八进制代码 014)作为记录分隔符):

$ cat data | sed $'s|^\(animal.*\)|\f\\1|' |
      ruby -014 -ne 'print if /type: cat/'

【讨论】:

在你给出的例子中,你可以设置RS="animal",我相信。 是的,这也可以,或者在这个特定的例子中是“\n\n”。我宁愿让 RS 实际上分离完整的记录,而不是消耗其中​​的任何一个。取决于数据格式。在这里,我想展示一些完全自定义的东西,因为这可以用于例如日志解析。【参考方案4】:

Awk 可以轻松检测模式的多行组合,但您需要在代码中创建所谓的 state machine 来识别序列。

考虑这个输入:

how
second half #1
now
first half
second half #2
brown
second half #3
cow

如您所见,识别单一模式很容易。现在,我们可以编写一个 awk 程序,该程序仅在 first half 行之前直接识别 second half。 (使用更复杂的状态机,您可以检测任意模式序列。)

/second half/ 
  if(lastLine == "first half") 
    print
  


 lastLine = $0 

如果你运行它,你会看到:

second half #2

现在,这个例子非常简单,只是一个状态机。有趣的状态仅在 if 语句的持续时间内持续,并且前面的状态是隐含的,取决于 lastLine 的值。 在更规范的状态机中,您将保留显式状态变量和从状态到状态的转换取决于现有状态和当前输入。但你可能不需要那么多控制机制。

【讨论】:

【参考方案5】:
`pcregrep -M` works pretty well for this.

来自 pcregrep(1):

-M, --多行

允许模式匹配多于一行。当给出这个选项时, 模式可能有用地包含文字换行符和内部 ^ 和 $ 字符的出现。成功匹配的输出 可能包括超过 一行,最后一行是比赛结束的那一行。如果 匹配的字符串以换行符结束输出结束于 该行的结尾。

设置此选项时,PCRE 库在“多行”中调用 模式。可以匹配的行数是有限制的, pcregrep 在扫描时缓冲输入文件的方式 它。但是,pcgrep 确保至少 8K 字符或其余字符 文件(以较短者为准)可用于转发 匹配,并且类似地之前的 8K 个字符(或所有 以前的字符,如果少于 8K) 保证可用 对于后向断言。输入时此选项不起作用 逐行读取(参见--line-buffered。)

【讨论】:

【参考方案6】:
awk '/pattern-start/,/pattern-end/'

ref

【讨论】:

以上是关于awk 模式可以匹配多行吗?的主要内容,如果未能解决你的问题,请参考以下文章

Python: 正则表达式匹配多行,实现多行匹配模式

grep 访问多行,查找两个模式之间的所有单词

Awk多模式匹配,并将输入文件的输出格式化为列。

如何使用 awk 打印匹配的正则表达式模式?

如何匹配和删除模式中的字符串

如何匹配awk中变量中给出的模式?