【问题标题】:How do you combine 2 awk output你如何结合2个awk输出
【发布时间】:2021-02-09 02:05:34
【问题描述】:

我有一个包含以下文本内容的文本文件,名为output.txt

1.2.2.2 LOCAL_IP
LOCAL_IP 1.1.1.1
1.1.1.1 LOCAL_IP
233.233.233.233 LOCAL_IP
123.123.123.123 LOCAL_IP
233.233.233.233 LOCAL_IP
231.231.231.231 LOCAL_IP
123.123.123.123 LOCAL_IP
LOCAL_IP 123.111.23.2
LOCAL_IP 221.22.22.22
1.1.1.1 LOCAL_IP
LOCAL_IP 1.2.2.2
LOCAL_IP 123.123.123.123
2.2.2.2 LOCAL_IP 
LOCAL_IP 3.3.21.2
LOCAL_IP 2.2.2.2
1.2.2.2 LOCAL_IP
LOCAL_IP 123.123.123.123
LOCAL_IP 123.111.23.2
1.1.1.1 LOCAL_IP

我想计算上面 IP 的总出现次数,不包括名为 LOCAL_IP 的文本字符串。例如,这是我的工作代码:

#!/bin/bash

output="output.txt"

a=$(awk '{ print $1 }' $output | grep -v 'LOCAL_IP' | sort | uniq -c | sed 's/^ *//' | sed -e "s/ /:/g")
b=$(awk '{ print $2 }' $output | grep -v 'LOCAL_IP' | sort | uniq -c | sed 's/^ *//' | sed -e "s/ /:/g")


echo "$a"
echo "-------"
echo "$b"

所以这是从上述脚本打印的输出(首先打印 a,然后是 b):

3:1.1.1.1
2:1.2.2.2
2:123.123.123.123
1:2.2.2.2
1:231.231.231.231
2:233.233.233.233
-------
1:1.1.1.1
1:1.2.2.2
2:123.111.23.2
2:123.123.123.123
1:221.22.22.22
1:2.2.2.2
1:3.3.21.2

有没有办法组合变量 ab 的结果,然后重新计算/更新结果 a 和 b 的总数(在符号 : 之前)?那么结合了 a 和 b 的新变量 c 的结果将类似于:

echo "$c"

4:1.1.1.1 # added from a and b
3:1.2.2.2
2:123.111.23.2
4:123.123.123.123
1:221.22.22.22
2:2.2.2.2
1:3.3.21.2
1:231.231.231.231
2:233.233.233.233

我不确定是否需要为此开发算法。有人可以阐明一下,也许有一种更简单的方法可以实现这一点。

【问题讨论】:

  • 所有答案都是关于在一个命令中完成所有操作。如果您真的想组合输出,您通常会在第二列 (sort -t: -k2) 上对两个结果进行排序,然后对于每一行,如果第二个字段与先前读取的字段匹配,则添加到前一个第一个字段。否则打印前一个第一个字段。您需要处理停止条件(文件中的最后一行会发生什么情况)等,但它是一种用任何常用脚本语言编写的相当简单的算法。

标签: bash shell awk sed grep


【解决方案1】:

很奇怪;这是我最近做的一个面试问题的一部分,涉及计算一个 IP 的流量。我因为其他原因退出了,所以你可以得到这份工作。 ;)

无论如何,有几种方法可以做到这一点。您可以使用 sed 或 awk 从输入中删除 LOCAL_IP。例如:

awk 'BEGIN{OFS=""} $1=="LOCAL_IP"{$1=""} $2=="LOCAL_IP"{$2=""} {print $0}' $output | ...
sed 's/ *LOCAL_IP *//' $output | ...

该 awk 需要您将 OFS 设置为空,否则由于具有特定结构的空字段(我不会使用;所有其他选项都更好),您最终会出现前导/尾随空格。

或者您可以使用正则表达式打印您想要的字段以匹配 IP。或者你可以做一些其他的事情,比如在管道到 grep 之前组合这两个命令:

{ awk '{ print $1 }' $output; awk '{ print $2 }' $output; } | grep ...

虽然真的,使用像 cut 这样的轻量级命令会更好。另外值得注意的是:当您只想要一个字段时,我偏爱awk '$0=$1',因为它的输入更少,并且在没有指定块时隐含{print $0}。 :)

或者只是使用tr 将空格替换为换行符,然后执行 grep。

tr ' ' '\n' $output | grep...

编辑: 基于上述@RavinderSingh13 的解决方案,您也可以在一个 awk 中完成所有操作:

awk '
  $1=="LOCAL_IP" { arr[$2]++ }
  $2=="LOCAL_IP" { arr[$1]++ }
  END{ for(i in arr){print arr[i]":"i} }
' $output

很多选择。 :D

【讨论】:

  • 我有一个可选问题:有没有办法将输出格式化,例如从 4:123.123.123 变成这样的 123.123.123.123 (4)。谢谢你的好回答
  • 使用 awk 或 sed 会很容易。在 awk 中:awk -F: '{print $2" ("$1")"}' 或在 sed 中:sed 's/\(.*\):\(.*\)/\2 (\1)/'
  • 如果您的 sed 支持 -E,则斜杠更少,更易于阅读,如 sed -E 's/(.*):(.*)/\2 (\1)/'
【解决方案2】:
awk '{if ($1=="LOCAL_IP") {print $2} else if($2=="LOCAL_IP"){print $1}}' output.txt |sort|uniq -c|sed 's/^ *//'

就像您已经做的那样,但不是将 IP 提取到 2 个不同的变量中,而是同时提取所有 IP。

只需检查第一个字段是否为LOCAL_IP,然后打印第二个字段,否则检查第二个字段是否为LOCAL_IP,然后打印第一个字段。

如果您的输出文件始终具有LOCAL_IP IPIP LOCAL_IP 结构,则不需要第二次比较:

awk '{if ($1=="LOCAL_IP") {print $2} else {print $1}}' output.txt |sort|uniq -c|sed 's/^ *//'

【讨论】:

    【解决方案3】:

    这可以在一个单独的 awk 中完成,请您尝试以下操作。写在手机上并在链接中测试 https://ideone.com/oKFxR7

    由于 OP 的示例所有行都有字符串 LOCAL_IP,所以我没有将该条件放入解决方案中,以防有人需要查找具有字符串的行,然后我们也可以简单地添加搜索条件以及匹配函数。

    awk '
    match($0,/([0-9]+\.){3}[0-9]+/){
      arr[substr($0,RSTART,RLENGTH)]++
    }
    END{
      for(i in arr){
        print arr[i]":"i
      }
    }
    ' Input_file
    

    解释:只需使用awkmatch 函数并在其中提供正则表达式来匹配IP 地址。然后创建名为 arr 的数组并将其索引作为匹配正则表达式的子字符串(其中 RSTARTRLENGTH 变量是默认变量,并从匹配的正则表达式中获取它们的值)。

    最后,当程序完成读取此代码的 END 块中的 Input_file 时,遍历 arr 数组并打印作为数组值的 IP 的出现和作为数组索引的打印 IP 地址。

    【讨论】:

    • 原来的问题是removing LOCAL_IP 来获取相反的字段,不匹配任何IP 地址。我假设,在实践中,$output 将包含两个 IP,它们是通过过滤 tcpdump 输出或类似的东西产生的,这使得细微的不同变得很重要。但是在 awk 中完成所有其他工作可能比大型管道更有效,因此您仍然会为这种改进获得支持。 :)
    • @dannysauer,是的,我同意。但老实说,我认为在一个 awk 中执行此操作更容易理解,也易于维护,太高兴了,学习愉快:)
    【解决方案4】:

    一个用于 GNU awk 的,用于使用 sorted_in 对输出进行排序,如果您不关心输出顺序,请放弃它:

    $ gawk '
    {
        a[$1]++                               # just count them all
        a[$2]++
    }
    END {                                     # and in the end
        delete a["LOCAL_IP"]                  # delete this one
        PROCINFO["sorted_in"]="@ind_str_asc"  # sorting method
        for(i in a)
            printf "%d:%s\n", a[i], i         # output
    }' file                                   # | sort -t: -k2
    

    如果您使用的是 GNU awk 以外的其他 awk,请取消注释 | sort -t: -k2 - 在这种情况下,您也可以删除 PROCINFO["sorted_in"]="@ind_str_asc"。另外,我在 cmets 中注意到重新格式化输出的请求;将printf 替换为printf "%s (%d)\n", i, a[i]

    输出:

    4:1.1.1.1
    3:1.2.2.2
    2:123.111.23.2
    4:123.123.123.123
    2:2.2.2.2
    1:221.22.22.22
    1:231.231.231.231
    2:233.233.233.233
    1:3.3.21.2
    

    【讨论】:

      【解决方案5】:

      在这种情况下我不会使用awk。这种方式是最简单的(对我来说)。

      1. 删除 LOCAL_IP 字符串:
      sed -E "s/ ?LOCAL_IP ?//" output.txt 
      1.2.2.2
      1.1.1.1
      1.1.1.1
      233.233.233.233
      123.123.123.123
      233.233.233.233
      231.231.231.231
      123.123.123.123
      123.111.23.2
      221.22.22.22
      1.1.1.1
      1.2.2.2
      123.123.123.123
      2.2.2.2
      3.3.21.2
      2.2.2.2
      1.2.2.2
      123.123.123.123
      123.111.23.2
      1.1.1.1
      
      1. 排序和唯一计数:
      sed -E "s/ ?LOCAL_IP ?//" output.txt  | sort | uniq -c
            4 1.1.1.1
            3 1.2.2.2
            2 123.111.23.2
            4 123.123.123.123
            1 221.22.22.22
            2 2.2.2.2
            1 231.231.231.231
            2 233.233.233.233
            1 3.3.21.2
      
      1. 根据需要格式化并按数量排序:
      sed -E "s/ ?LOCAL_IP ?//" output.txt  | sort | uniq -c | sed -E 's/^ *([0-9]+) *(.*)$/\1:\2/' | sort -rn
      4:123.123.123.123
      4:1.1.1.1
      3:1.2.2.2
      2:233.233.233.233
      2:2.2.2.2
      2:123.111.23.2
      1:3.3.21.2
      1:231.231.231.231
      1:221.22.22.22
      

      所有这些步骤都很容易遵循。除了带有反向引用的最后一个正则表达式。要了解更多关于正则表达式的信息,请将正则表达式放入https://regex101.com/ 并阅读说明。

      【讨论】:

      • 如果ip有ipv6格式怎么办。它可以包含 ipv6 格式。
      • 测试一下。它适用于 ipv4 和 ipv6。但是你也应该完成你的问题。最好也包括一个 ipv6 的示例,那么起点和预期结果是什么。这两个 IP 版本是否应该混合使用?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-07
      • 2011-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-30
      相关资源
      最近更新 更多