awk模式可以匹配多行吗?

时间:2013-01-16 03:19:37

标签: linux awk

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

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

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

另一种看待这种情况的方法是,我希望在日志文件中隔离几行,并根据几行上的模式匹配打印出那些作为特定pkt细节的行。

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

如果awk可以做到这一点,怎么做?如果没有,有关于使用哪种工具的建议吗?

5 个答案:

答案 0 :(得分:19)

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

考虑这个输入:

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

如您所见,识别单一模式很容易。现在,我们可以编写一个awk程序,只有在上半部分行之前才会识别后半部分。 (使用更复杂的状态机,您可以检测到任意序列的模式。)

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

{ lastLine = $0 }

如果你运行这个,你会看到:

second half #2

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

答案 1 :(得分:10)

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表示使用form-feed(八进制代码014)作为记录分隔符):

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

答案 2 :(得分:6)

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

答案 3 :(得分: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相匹配的行。代码的结构当然是日志文件结构的产物,因此您需要为您尝试分析和提取的数据自定义解决方案。

答案 4 :(得分:0)

`pcregrep -M` works pretty well for this.

来自pcregrep(1):

  

-M, - multiline

     

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

     

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