【问题标题】:Sorting log file into groups with bash and awk使用 bash 和 awk 将日志文件分组
【发布时间】:2015-04-20 07:20:27
【问题描述】:

我正在尝试以特定方式对日志文件进行排序,但我不确定如何执行最后一步。

我的日志文件有这样的条目:

Feb 15 17:00:34 server sshd[13879]: Invalid user test from 200.242.94.133
Feb 15 17:00:35 server sshd[13780]: Invalid user ftpuser from 200.242.94.133
Feb 15 17:01:34 server sshd[13890]: Invalid user test from 200.242.94.133
Feb 15 17:01:35 server sshd[13791]: Invalid user vnc from 200.242.94.133
Feb 15 17:01:35 server sshd[13794]: Invalid user test from 50.63.172.108
Feb 15 17:01:36 server sshd[13798]: Invalid user vnc from 50.63.172.108

我使用命令:

cat logfile | grep "Invalid user" | awk '{print $8 ", " $10 }' | sort -t":" -k2,2 | uniq -c

哪些输出:

 1 ftpuser, 200.242.94.133
 2 test, 200.242.94.133
 1 test, 50.63.172.108 
 1 vnc, 200.242.94.133
 1 vnc, 50.63.172.108

我想得到:

1 ftpuser, (1) 200.242.94.133
3 test, (2) 200.242.94.133, (1) 50.63.172.108
2 vnc, (1) 200.242.94.133, (1) 50.63.172.108

我不确定如何对 words 列求和,同时保持 ip 地址单独计数,然后将其与其他结果一起包含。

尝试回答:

# awk '/Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}' logfile | sort -k2
awk: /Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}
awk:                                 ^ syntax error
awk: /Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}
awk:                                                                                                   ^ syntax error
awk: /Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}
awk:                                                                                                                               ^ syntax error

【问题讨论】:

    标签: regex bash shell sorting awk


    【解决方案1】:
    $ awk '/Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}' logfile
    2 vnc, (1) 50.63.172.108, (1) 200.242.94.133
    1 ftpuser, (1) 200.242.94.133
    3 test, (1) 50.63.172.108, (2) 200.242.94.133
    

    如果您希望它按用户字母顺序排序:

    $ awk '/Invalid user/{user[$8]++;ip[$8][$10]++} END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}' logfile | sort -k2
    1 ftpuser, (1) 200.242.94.133
    3 test, (1) 50.63.172.108, (2) 200.242.94.133
    2 vnc, (1) 50.63.172.108, (1) 200.242.94.133
    

    以上内容适用于 GNU awk。我没有用 BSD 测试过。

    工作原理

    • /Invalid user/{user[$8]++;ip[$8][$10]++}

      对于logfile 中包含无效用户的任何行,这会计算用户名,字段 8 和 IP 地址,字段 10。

    • END{for (u in user){printf "%s %s",user[u],u;for (i in ip[u])printf ", (%s) %s",ip[u][i],i;print""}}

      当我们读完logfile 后,它会遍历我们见过的每个用户,并打印我们见过该用户的次数,然后是该用户的姓名,然后是每个 IP 地址的计数对于那个 ip,然后是那个 ip。

    【讨论】:

    • 向优雅和简洁致敬。需要 GNU awk;不适用于 BSD awk 或 mawk(缺少真正的多维数组)。需要注意的是,由于关联数组的未排序枚举,IP 地址的顺序似乎是随机的。在 gawk 4.0+ 上,为了至少获得字母排序,您可以添加 PROCINFO["sorted_in"]="@ind_str_asc" 作为 END 块中的第一条语句 - 这也使得 sort 命令变得不必要。至于sort 命令:最好使用sort -k2,2sort -k2 不必要地比较名称字段加上行的其余部分)。
    • 非常感谢您抽出宝贵时间提出这个问题 - 我正在使用GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu),似乎得到了syntax error,显示这部分ip[u][i],
    • @JoeHabadas 使用该版本的awk,它应该可以工作。我只是从答案中复制并粘贴并再次运行代码,它仍然对我有用。使用awk,如果在错误的地方只有一个空格,您可能会收到语法错误。例如,插入ip[u] [i] 之类的空格会产生语法错误。您会再试一次,如果再次失败,请将代码和错误消息全部复制并粘贴到您的问题中吗?只是把它作为问题的结尾。 (不要尝试将其粘贴到注释中:粘贴多行时,cmets 会破坏格式。)
    • @John1024,嗨,约翰,我已按要求在我的答案中发布了结果。
    • @mklement0,升级到GNU Awk 4.1.1, API: 1.1 成功了。再次感谢您的帮助。
    【解决方案2】:

    John1024's answer 是一个非常简洁且可能快速的解决方案,如果:

    • 您正在使用 GNU awk(该解决方案使用非 POSIX 功能,这些功能不适用于 BSD awk(也用于 OS X)或 mawk,例如)。
    • 您不介意看似随机的 IP 地址顺序(由于关联数组的未排序键枚举;但是,在 GNU awk 4.0+ 中,您可以使用 PROCINFO["sorted_in"] to control the enumeration order)。

    这里有一个更简单的解决方案,但是:

    • 仅使用 POSIX awk 功能。
    • 按输入中遇到的顺序列出 IP 地址。

    它建立在 OP 命令的略微简化版本之上。

    awk '/Invalid user/ { print $8 ", " $10 }' logfile | sort -t":" -k2,2 | uniq -c |
    awk '
        # Helper output function for printing an output line.
      function printLine(c, n, l) { 
        sub(/,$/, "", n); print c, n l
      }
        # End of previous block found (new username)?
      prevName != $2 {
          # Print summary line for previous block.
        if (NR>1) printLine(count, prevName, ipList)
          # Start new block.
        prevName=$2; count=0; ipList=""
      }
        # Build block summary values.
      { 
        count+=$1
        ipList=ipList ", (" $1 ") " $3
      }
        # Print summary line for last block.
      END { 
        printLine(count, prevName, ipList)
      }
      '
    

    【讨论】:

    • 感谢您的解决方案。如何使用辅助功能?我在最初的尝试之后尝试将其粘贴,并尝试用作 shell 脚本,但似乎无法获得输出。对不起,如果我听起来像个菜鸟,只是不确定它是如何工作的。
    • @JoeHabadas:我不完全确定您尝试了什么,但请注意帮助函数是 awk 程序的一部分,而不是 shell 功能:第 2 行的所有内容都是一个 awk 程序,在多行单引号字符串中,因此您需要粘贴整个内容。
    • 选择哪个答案是一个艰难的选择,但我决定选择John1024's,主要是因为它很简单——不过,我真的很感谢你花时间想出这个。干杯!
    • 我很高兴,@JoeHabadas。如果可以使用 GNU awk,John 的回答无疑是更好的选择。
    猜你喜欢
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    相关资源
    最近更新 更多