awk 无法处理 sed 的管道输出

awk 无法处理 sed 的管道输出

我有一个日志文件,大致如下所示:

Sep 23 10:28:26 node kernel: em0: device is going DOWN
Sep 23 10:28:26 node kernel: em0: device is going UP
Sep 23 10:29:14 node cdsmon: /tmp/instance0 ; core dumped
Sep 23 10:29:14 node cdsmon: /tmp/instance0 ; core dumped
Sep 23 10:28:26 node kernel: em0: device is going DOWN
Sep 23 10:29:14 node cdsmon: /tmp/instance1 ; core dumped
Sep 23 10:28:26 node kernel: em0: device is going UP
Sep 23 10:29:14 node cdsmon: /tmp/instance2 ; core dumped

我想检测 的行,cdsmon然后将行分割;(以获取/tmp/instance0和 类似的事件core dumped)。

为此我用作sed

sed -u -n -e "s/^.*cdsmon: //p" /tmp/dev.log

其输出为:

/tmp/instance0 ; core dumped
/tmp/instance0 ; core dumped
/tmp/instance1 ; core dumped
/tmp/instance2 ; core dumped

但是,将此输出通过管道传输到awk如下所示时,它会给出与上面相同的输出:

sed -u -n -e "s/^.*cdsmon: //p" /tmp/dev.log | awk -F ";" "{print $1}"

-u尽管从 中删除了该选项,但还是观察到了同样的情况sed

如果我遗漏了什么,有人可以指出吗?我正在使用带有常规 awk/sed 的 FreeBSD 盒子,不幸的是无法安装任何新软件包。

答案1

的行为的原因awk是您已将程序包含在双倍的引号,使字符串对 shell 的变量扩展开放。这意味着运行程序的 shell 将首先展开$1,并且由于这可能是未定义的,因此它会展开为空字符串。

所以,你的程序相当于

awk -F ";" "{print}"

这就是打印整行的原因。这是您应该始终将您的awk(和sed)程序包含在其中的原因之一单身的引号。

请注意,在大多数情况下,您不需要将输出通过管道传输sedawk,反之亦然。在您的示例中,如果您想获取“事件标签”之后的第一个字段,您可以执行以下操作:

sed -E -n 's/^.*cdsmon: ([^;]*).*$/\1/p' /tmp/dev.log 

这将定义一个捕获组cdsmon:围绕第一个之后的字符串;,并将整行替换为该捕获组的内容。

如果您想打印 记录的事件的摘要cdsmon,您可以将sed上述方法扩展为:

sed -E -n 's/^.*cdsmon: ([^;]*) ; (.*)$/\1 : \2/p' dev.log 

或者,这是另一种awk-only 方法:

awk -F'(cdsmon: | ; )' 'NF==3{printf "%s : %s\n",$2,$3}' dev.log 

对于您的示例,两者都会打印

/tmp/instance0 : core dumped
/tmp/instance0 : core dumped
/tmp/instance1 : core dumped
/tmp/instance2 : core dumped

但请注意,该awk方法可能会遇到边缘情况。它采用模式cdsmon:;作为字段分隔符。当有三个字段时(在您的示例中,它只能发生在条目中cdsmon:),它会打印第二个和第三个字段,对应于 after 的实例名称cdsmon:和 after 的原因;

答案2

我会awk在整个操作中使用。在这里,我按冒号进行分割,因此在考虑日期/时间后,必须将主机匹配应用于第三个字段(14 node cdsmon例如):

awk -F: '
    $3 ~ / cdsmon$/ {
        split($4, text, / *; */);    # Split field at semicolon
        sub(/^ */, "", text[1]);     # Remove leading space
        printf "instance %s, reason %s\n", text[1], text[2]
    }
' /tmp/dev.log

这是在中建议的替代且更简单的解决方案评论,我们用冒号或分号进行分割,因此必要的字段已经直接在awk变量中:

awk -F': | *; *' '
    $1 ~ / cdsmon$/ { printf "instance %s, reason %s\n", $2, $3 }
' /tmp/dev.log

您没有说明要如何提取实例和原因(或者如果您这样做了,我错过了),所以我只是将它们打印在字符串中,证明它们已被正确提取。

答案3

根据说明书:

双引号可以保护开盘价和收盘价之间的大部分内容。 shell 至少对引用的文本进行变量和命令替换。不同的 shell 可能会对双引号文本进行其他类型的处理。

由于双引号文本中的某些字符由 shell 处理,因此必须在文本中对它们进行转义。值得注意的是字符 '$'、'``'、'\' 和 '"',如果要按字面传递给程序,所有这些字符都必须在双引号文本中前面有一个反斜杠。

所以在你的情况下你可以逃避美元符号$

sed -u -n -e "s/^.*cdsmon: //p" /tmp/dev.log | awk -F ";" "{print \$1}"

但使用单引号更容易:

sed -u -n -e "s/^.*cdsmon: //p" /tmp/dev.log | awk -F ' ; ' '{ print $1 }'

您还可以在分隔符之间留出空格,' ; '这样每行后面就不会出现不可见的空格。

您也可以只使用awk

$ awk -F': | ; ' '/cdsmon/ { print $2 }' /tmp/dev.log
/tmp/instance0
/tmp/instance0
/tmp/instance1
/tmp/instance2

答案4

awk '{for(i=1;i<=NF;i++){if($i ~ /cdsmon/){print $(i+1),$(i+3),$(i+4)}}}' filename

输出

/tmp/instance0 core dumped
/tmp/instance0 core dumped
/tmp/instance1 core dumped
/tmp/instance2 core dumped





awk '{for(i=1;i<=NF;i++){if($i ~ /cdsmon/){print $(i+1)}}}' filename
/tmp/instance0
/tmp/instance0
/tmp/instance1
/tmp/instance2

相关内容