使用jq在多个层级上进行计数

11
我们已经找到了与感染有关的一些域名。现在我们有一个.json文件列出了DNS名称列表,我想生成一个总结输出,显示:用户列表,他们访问的唯一域名和总计数。如果我还可以获得每个域名的计数,则额外加分。
以下是文件示例:
{"machine": "possible_victim01", "domain": "evil.com", "timestamp":1435071870}
{"machine": "possible_victim01", "domain": "evil.com", "timestamp":1435071875}
{"machine": "possible_victim01", "domain": "soevil.com", "timestamp":1435071877}
{"machine": "possible_victim02", "domain": "bad.com", "timestamp":1435071877}
{"machine": "possible_victim03", "domain": "soevil.com", "timestamp":1435071879}

理想情况下,我希望输出的结果类似于:

{"possible_victim01": "total": 3, {"evil.com": 2, "soevil.com": 1}}
{"possible_victim02": "total": 1, {"bad.com": 1}}
{"possible_victim03": "total": 1, {"soevil.com": 1}}

我很乐意妥协接受:

{"possible_victim01": "total": 3, ["evil.com", "soevil.com"]}
{"possible_victim02": "total": 1, ["bad.com"]}
{"possible_victim03": "total": 1, ["soevil.com"]}

我可以获取每个用户的总记录数,但会失去域名列表:

cat sample.json | jq -s 'group_by(.machine) | map({machine:.[0].machine,domain:.[0].domain, count:length}) '
[{"machine": "possible_victim01", "domain": "evil.com", "count": 3},  
{"machine": "possible_victim02", "domain": "bad.com", "count": 1},
{"machine": "possible_victim03", "domain": "soevil.com", "count": 1}]

本文介绍如何解决问题的后半部分......JQ聚合和交叉表。目前为止,我还没有找到任何描述第一部分的内容,即如何达成以下结果:

{"machine": "possible_victim01", "domain": "evil.com", "count":2}
{"machine": "possible_victim01", "domain": "soevil.com", "count":1}
{"machine": "possible_victim02", "domain": "bad.com", "count":1}
{"machine": "possible_victim03", "domain": "soevil.com", "count":1}
3个回答

16

你需要执行两次 group_by,第一次按机器名称分组,第二次进行子分组以获取每个域的子计数。

jq查询:

group_by(.machine) | map({
    "machine": .[0].machine, 
    "total":length, 
    "domains": (group_by(.domain) | map({
        "key":.[0].domain, 
        "value":length}) | from_entries
    )
})

示例输出:

{
  "machine": "possible_victim01",
  "total": 3,
  "domains": {
    "evil.com": 2,
    "soevil.com": 1
  }
}
{
  "machine": "possible_victim02",
  "total": 1,
  "domains": {
    "bad.com": 1
  }
}
{
  "machine": "possible_victim03",
  "total": 1,
  "domains": {
    "soevil.com": 1
  }
}

3
使用group_by按照上述方式是可以的,但是如果您需要读取非常多的行(即JSON实体),如示例所提供的建议,那么您可能会遇到性能问题和/或容量限制。
在任何版本的jq中(例如jq 1.5rc1),这些问题都可以通过使用“inputs”内置功能来非常有效地解决。
请注意,使用“inputs”,您将使用-n选项调用jq,就像这样:
jq -n -f program.jq data.json

请注意,这里最好生成JSON输出,并且以下内容似乎接近所需的内容:
{"possible_victim01": { "total": 3, "evildoers": {"evil.com": 2, "soevil.com": 1} },
 "possible_victim02": ...}`

以下程序可以更加简洁,但在这里的呈现旨在使过程透明化,假设您有基本的jq理解。如果这里有什么神奇之处,那就是不必特别处理 "null"。
reduce inputs as $line
  ({};
   . as $in
   | ($line.machine) as $machine
   | ($line.domain) as $domain
   | ($in[$machine].evildoers ) as $evildoers
   | . + { ($machine): {"total": (1 + $in[$machine]["total"]),
                        "evildoers": ($evildoers | (.[$domain] += 1)) }} )

使用所提供的示例输入,输出结果如下:
{
  "possible_victim01": {
    "total": 3,
    "evildoers": {
      "evil.com": 2,
      "soevil.com": 1
    }
  },
  "possible_victim02": {
    "total": 1,
    "evildoers": {
      "bad.com": 1
    }
  },
  "possible_victim03": {
    "total": 1,
    "evildoers": {
      "soevil.com": 1
    }
  }
}

3

这里有一个解决方案,使用reduce, getpathsetpath

reduce .[] as $o (
  {}
; [$o.machine, "total"] as $p1
| [$o.machine, "domains", $o.domain] as $p2
| setpath($p1; 1+getpath($p1))
| setpath($p2; 1+getpath($p2))
)

如果filter.jq包含此过滤器,data.json包含示例数据,则命令为:
$ jq -M -s -f filter.jq data.json

生产

{
  "possible_victim01": {
    "total": 3,
    "domains": {
      "evil.com": 2,
      "soevil.com": 1
    }
  },
  "possible_victim02": {
    "total": 1,
    "domains": {
      "bad.com": 1
    }
  },
  "possible_victim03": {
    "total": 1,
    "domains": {
      "soevil.com": 1
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接