使用 jq 获取列表项的每个类别计数

Getting per-category counts of list items with jq

我目前正在学习如何在 Linux 中将 jq 与 shell 一起使用,因为我正在为 Check_MK(以前称为 Nagios)和我的应用程序(qBittorrent用他们的 WebUI API) returns JSON 字符串。

目前,我已经能够仅使用一个简单的 jq length 来计算种子的总数。现在,我想计算当前正在下载、播种或暂停的种子的数量。我只对 state 感兴趣,所以如果我有 6 个种子,我的 JSON 可能看起来像这样:

[
  {
    "state": "uploading"
  },
  {
    "state": "downloading"
  },
  {
    "state": "downloading"
  },
  {
    "state": "downloading"
  },
  {
    "state": "pauseDL"
  },
  {
    "state": "pauseUP"
  }
]

这里,jq length returns 6. 3正在下载,1正在上传,2暂停,0错误,我需要怎么做?

这是我的实际脚本:

#!/bin/sh
curl -s http://localhost:8080/query/torrents -o /tmp/torrents.json
count=$(jq length /tmp/torrents.json)
echo "0 qbt_Nb_torrents - $count"

Check_MK 需要 echo 的语法(如 here 所述)。

我已经阅读了多个有关过滤器的示例,但当我们通过顶级属性进行过滤时,它们似乎都有效。在这里,我的顶级基本上只是 [0],...,[5],所以它不适用于我在手册中找到的示例。

附加信息

WebUI API 表示有 12 种不同的可能状态。这就是我打算将它们分开的方式:

downloading: queuedDL, checkingDL, downloading 
uploading: queuedUP, checkingUP, uploading 
pause: pausedUP, pausedDL 
error: error 
stalled: stalledUP, stalledDL, metaDL

根据 CheckMK 语法,我基本上需要输出如下内容:

0 qbt_Nb_torrents - 6 total, 3 downloading, 1 seeding, 2 on pause, 0 stalled, 0 error

开头的第一个0表示CheckMK的OK状态。如果有任何停滞的种子,我希望该状态变为 1,如果有任何种子错误,则状态变为 2。示例:

2 qbt_Nb_torrents - 8 total, 3 downloading, 1 seeding, 2 on pause, 1 stalled, 1 error

对于有相关问题但未分享 OP 的具体要求的其他人:请参阅编辑历史记录!在此答案的先前迭代中,还有其他几个相关提案,包括 group_by 使用。


如果您需要 所有 值的条目,即使是没有出现的条目,您可以考虑:

jq -r '
  def filterStates($stateMap):
    if $stateMap[.] then $stateMap[.] else . end;

  def errorLevel:
    if (.["error"] > 0) then 2 else
      if (.["stalled"] > 0) then 1 else
        0
      end
    end;

  {"queuedDL": "downloading", 
   "checkingDL": "downloading",
   "queuedUP": "uploading", 
   "checkingUP": "uploading",
   "pausedUP": "pause", 
   "pausedDL": "pause",
   "stalledUP": "stalled", 
   "stalledDL": "stalled", 
   "metaDL": "stalled"} as $stateMap |

  # initialize an output array since we want 0 outputs for everything
  {"pause": 0,  "stalled": 0, "error": 0, "downloading": 0, "uploading": 0} as $counts |

  # count number of items which filter to each value
  reduce (.[].state | filterStates($stateMap)) as $state ($counts; .[$state]+=1) |

  # actually format an output string
  "\(. | errorLevel) qbt_Nb_torrents - \(values | add) total, \(.["downloading"]) downloading, \(.["uploading"]) seeding, \(.["pause"]) on pause, \(.["stalled"]) stalled, \(.["error"]) error"
' /tmp/torrents.json

以防万一有人想知道我在 Charles Duffy 的出色回答之后到底使用了什么,这里是完整的 /usr/lib/check_mk_agent/local/qbittorrent shell 脚本,它允许 Check_MK (1.5.0 raw ) 以获取我认为与我的 qBittorrent 应用程序 (qBittorrent v3.3.7 Web UI) 运行 在我服务器上的专用 VM 中最相关的信息:

#!/bin/sh
curl -s http://localhost:8080/query/transferInfo -o /tmp/transferInfo.json
curl -s http://localhost:8080/query/torrents -o /tmp/torrents.json

if [ -e /tmp/transferInfo.json ]
then
 dwl=$(jq .dl_info_speed /tmp/transferInfo.json)
 dwl_MB=$(bc <<< "scale=2;$dwl/1048576")
 upl=$(jq .up_info_speed /tmp/transferInfo.json)
 upl_MB=$(bc <<< "scale=2;$upl/1048576")
 echo "0 qbt_Global_speed download=$dwl_MB|upload=$upl_MB Download: $dwl_MB MB/s, Upload: $upl_MB MB/s"
 rm -f /tmp/transferInfo.json
else
 echo "3 qbt_Global_speed download=0|upload=0 Can't get the information from qBittorrent WebUI API"
fi

if [ -e /tmp/torrents.json ]
then    
 jq -r '
   def filterStates($stateMap):
    if $stateMap[.] then $stateMap[.] else . end;

   {"queuedDL": "downloading",
   "checkingDL": "downloading",
   "queuedUP": "uploading",
   "checkingUP": "uploading",
   "pausedUP": "pause",
   "pausedDL": "pause",
   "stalledUP": "stalled",
   "stalledDL": "stalled",
   "metaDL": "stalled"} as $stateMap |

   # initialize an output array since we want 0 outputs for everything
   {"pause": 0,  "stalled": 0, "error": 0, "downloading": 0, "uploading": 0} as $counts |

   # count number of items which filter to each value
   reduce (.[].state | filterStates($stateMap)) as $state ($counts; .[$state]+=1) |

   # output string
   "P qbt_Nb_torrents total=\(values|add)|downloading=\(.["downloading"])|seeding=\(.["uploading"])|pause=\(.["pause"])|stalled=\(.["stalled"]);0|error=\(.["error"]);0;0 total is \(values|add), downloading is \(.["downloading"]), seeding is \(.["uploading"]), pause is \(.["pause"])"
' /tmp/torrents.json

 rm -f /tmp/torrents.json
else
 echo "3 qbt_Nb_torrents total=0|downloading=0|seeding=0|pause=0|stalled=0;0|error=0;0;0 Can't get the information from qBittorrent WebUI API"
fi

这是 1 个停滞的 torrent 的输出:

0 qbt_Global_speed download=0|upload=0 Download: 0 MB/s, Upload: 0 MB/s
P qbt_Nb_torrents total=1|downloading=0|seeding=0|pause=0|stalled=1;0|error=0;0;0 total is 1, downloading is 0, seeding is 0, pause is 0

由于 Check_MK 的工作方式,我认为我需要的 errorLevel(请参阅 Charles 的回答)不是必需的;当指定警告和临界值时,它通过 the metric parameter 自行处理阈值。

这是 Check_MK 中的样子:

注意 Check_MK 如何自动添加停滞和错误的种子文件。这是因为指定了警告 and/or 临界阈值。一般指标(有或没有阈值)对于提供详细的相关图表很重要。

这是@CharlesDuffy 解决方案的模块化版本。主要兴趣点可能是通用 "bag of words" 过滤器:

# bag of words
def bow(init; s): reduce s as $word (init; .[$word] += 1) ;

还要注意初始化函数:

# initialize an output object since we minimally want 0s
def init:
  {} | {pause,stalled,error,downloading,uploading} | map_values(0);

有了这些额外的抽象,"main" 程序就变成了两行代码。

  def filterStates($stateMap):
    if $stateMap[.] then $stateMap[.] else . end ;

  def errorLevel:
    if .error > 0 then 2
    elif .stalled > 0 then 1
    else 0
    end ;

  def stateMap:
    {"queuedDL": "downloading", 
     "checkingDL": "downloading",
     "queuedUP": "uploading", 
     "checkingUP": "uploading",
     "pausedUP": "pause", 
     "pausedDL": "pause",
     "stalledUP": "stalled", 
     "stalledDL": "stalled", 
     "metaDL": "stalled"} ;

"Main"

  # count number of items which map to each value
  bow(init; .[].state | filterStates(stateMap))
  # format an output string
  | "\(errorLevel) qbt_Nb_torrents - \(values | add) total, \(.downloading) downloading, \(.uploading) seeding, \(.pause) on pause, \(.stalled) stalled, \(.error) error"