Как я могу фильтровать по числовому полю с помощью jq?

Я пишу скрипт для запроса API Bitbucket и удаления артефактов SNAPSHOT, которые никогда не загружались. Этот сценарий не работает, потому что он получает ВСЕ артефакты моментальных снимков, выбор количества загрузок, похоже, не работает.

Что не так с моим оператором select для фильтрации объектов по количеству загрузок?

Конечно, более прямое решение здесь было бы, если бы я мог просто запросить API Bitbucket с помощью фильтра. Насколько мне известно, API не поддерживает фильтрацию по загрузкам.

Мой сценарий:

#!/usr/bin/env bash
curl -X GET --user "me:mykey" "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads?pagelen=100" > downloads.json

# get all values | reduce the set to just be name and downloads | select entries where downloads is zero | select entries where name contains SNAPSHOT | just get the name
#TODO i screwed up the selection somewhere its returning files that contain SNAPSHOT regardless of number of downloads
jq '.values | {name: .[].name, downloads: .[].downloads} | select(.downloads==0) | select(.name | contains("SNAPSHOT")) | .name' downloads.json > snapshots_without_any_downloads.js
#unique sort, not sure why jq gives me multiple values
sort -u snapshots_without_any_downloads.js | tr -d '"' > unique_snapshots_without_downloads.js

cat unique_snapshots_without_downloads.js | xargs -t -I % curl -Ss -X DELETE --user "me:mykey" "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads/%" > deleted_files.txt

Деидентифицированный образец необработанного ввода из API:

{
  "pagelen": 10,
  "size": 40,
  "values": [
    {
      "name": "myproject_1.1-SNAPSHOT_0210f77_mc_3.5.0.zip",
      "links": {
        "self": {
          "href": "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads/myproject_1.1-SNAPSHOT_0210f77_mc_3.5.0.zip"
        }
      },
      "downloads": 2,
      "created_on": "2018-03-15T17:50:00.157310+00:00",
      "user": {
        "username": "me",
        "display_name": "me",
        "type": "user",
        "uuid": "{3051ec5f-cc92-4bc3-b291-38189a490a89}",
        "links": {
          "self": {
            "href": "https://api.bitbucket.org/2.0/users/me"
          },
          "html": {
            "href": "https://bitbucket.org/me/"
          },
          "avatar": {
            "href": "https://bitbucket.org/account/me/avatar/32/"
          }
        }
      },
      "type": "download",
      "size": 430894
    },
    {
      "name": "myproject_1.1-SNAPSHOT_thanks_for_the_reminder_charles_duffy_mc_3.5.0.zip",
      "links": {
        "self": {
          "href": "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads/myproject_1.1-SNAPSHOT_0210f77_mc_3.5.0.zip"
        }
      },
      "downloads": 0,
      "created_on": "2018-03-15T17:50:00.157310+00:00",
      "user": {
        "username": "me",
        "display_name": "me",
        "type": "user",
        "uuid": "{3051ec5f-cc92-4bc3-b291-38189a490a89}",
        "links": {
          "self": {
            "href": "https://api.bitbucket.org/2.0/users/me"
          },
          "html": {
            "href": "https://bitbucket.org/me/"
          },
          "avatar": {
            "href": "https://bitbucket.org/account/me/avatar/32/"
          }
        }
      },
      "type": "download",
      "size": 430894
    },
    {
      "name": "myproject_1.0_mc_3.5.1.zip",
      "links": {
        "self": {
          "href": "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads/myproject_1.1-SNAPSHOT_0210f77_mc_3.5.1.zip"
        }
      },
      "downloads": 5,
      "created_on": "2018-03-15T17:49:14.885544+00:00",
      "user": {
        "username": "me",
        "display_name": "me",
        "type": "user",
        "uuid": "{3051ec5f-cc92-4bc3-b291-38189a490a89}",
        "links": {
          "self": {
            "href": "https://api.bitbucket.org/2.0/users/me"
          },
          "html": {
            "href": "https://bitbucket.org/me/"
          },
          "avatar": {
            "href": "https://bitbucket.org/account/me/avatar/32/"
          }
        }
      },
      "type": "download",
      "size": 430934
    }
  ],
  "page": 1,
  "next": "https://api.bitbucket.org/2.0/repositories/myemployer/myproject/downloads?pagelen=10&page=2"
}

Результат, который я хочу от этого фрагмента, myproject_1.1-SNAPSHOT_thanks_for_the_reminder_charles_duffy_mc_3.5.0.zip - этот артефакт является SNAPSHOT и не имеет загрузок.

Я использовал этот промежуточный шаг для отладки:

jq '.values | {name: .[].name, downloads: .[].downloads} | select(.downloads>0) | select(.name | contains("SNAPSHOT")) | unique' downloads.json > snapshots_with_downloads.js
jq '.values | {name: .[].name, downloads: .[].downloads} | select(.downloads==0) | select(.name | contains("SNAPSHOT")) | .name' downloads.json > snapshots_without_any_downloads.js
#this returns the same values for each list!
diff unique_snapshots_with_downloads.js unique_snapshots_without_downloads.js

Эта корректировка дает более чистую и уникальную структуру, она предполагает, что есть какой-то аспект разделения или потоковой передачи jq, который я не совсем понимаю:

#this returns a "unique" array like I expect, adding select to this still does not produce the desired outcome 
jq '.values | [{name: .[].name, downloads: .[].downloads}] | unique' downloads.json

Данные после этого шага выглядят так. Он просто удалил лишнее, что мне не нужно, из необработанного ответа API:

[
  {
    "name": "myproject_1.0_2400a51_mc_3.4.0.zip",
    "downloads": 0
  },
  {
    "name": "myproject_1.0_2400a51_mc_3.4.1.zip",
    "downloads": 2
  },
  {
    "name": "myproject_1.1-SNAPSHOT_391f4d5_mc_3.5.0.zip",
    "downloads": 0
  },
  {
    "name": "myproject_1.1-SNAPSHOT_391f4d5_mc_3.5.1.zip",
    "downloads": 2
  }
]

person Freiheit    schedule 16.03.2018    source источник
comment
минимальный воспроизводимый пример будет включать в себя некоторый образец JSON - в идеале, что-то как можно более простое, чтобы проиллюстрировать то, что вы пытается сделать, а затем кратчайший jq код, который пытается это сделать.   -  person Charles Duffy    schedule 16.03.2018
comment
Спасибо за добавление данных. Тем не менее, запуская jq '.values | {name: .[].name, downloads: .[].downloads}' <test.json с вашим образцом JSON (начиная с версии 2 вопроса), в наборе нет 0, поэтому select(.downloads==0) не имеет особого смысла.   -  person Charles Duffy    schedule 16.03.2018
comment
Кстати, == "0" ожидает строку, тогда как == 0 для целого числа. Не уверен, почему первое присутствует где-либо в коде.   -  person Charles Duffy    schedule 16.03.2018
comment
0 против 0 - это более бесполезная отладка. Спасибо, что поддержал меня, некоторые небрежные шаги по отладке были скопированы в вопрос.   -  person Freiheit    schedule 16.03.2018
comment
Обратите внимание на jq -r, если вы не хотите, чтобы контент выводился в формате JSON (с начальными и конечными кавычками).   -  person Charles Duffy    schedule 16.03.2018
comment
stedolan.github.io/jq/manual/#inputs также является новым для меня , Выводит все оставшиеся входы один за другим.   -  person Freiheit    schedule 16.03.2018
comment
Правильно -- используется в сочетании с -n (чтобы первый ввод не был съеден как начальный контекст).   -  person Charles Duffy    schedule 16.03.2018
comment
Это, возможно, излишне, поскольку наш входной файл имеет только один объект JSON (по сравнению со способностью jq обрабатывать потоки с несколькими объектами). Ценность в том, что это позволяет нам продемонстрировать практику, которая гарантированно обеспечивает глобально уникальные результаты даже при более интересных входных данных.   -  person Charles Duffy    schedule 16.03.2018
comment
Кстати, не стесняйтесь добавлять свой собственный ответ как отдельный ответ, а не предлагать его как редактирование.   -  person Charles Duffy    schedule 16.03.2018


Ответы (2)


Как я понимаю:

  • Вам нужны глобально уникальные результаты
  • Вам нужны только элементы с downloads==0
  • Вам нужны только элементы, название которых содержит «SNAPSHOT».

Это будет выполнено следующим образом:

jq -r '
[.values[] | {(.name): .downloads}]
| add
| to_entries[]
| select(.value == 0)
| .key | select(contains("SNAPSHOT"))'

Вместо того, чтобы делать unique явным шагом, эта версия создает карту от имен до счетчиков загрузок (addобъединение значений вместе, что означает, что в случае конфликтов выигрывает последнее), и, таким образом, оба обеспечивают уникальность выходных данных.


Учитывая ваш тестовый JSON, вывод:

myproject_1.1-SNAPSHOT_thanks_for_the_reminder_charles_duffy_mc_3.5.0.zip

Применительно к общему контексту проблемы эту стратегию можно использовать для упрощения всего процесса:

jq -r '[.values[] | {(.links.self.href): .downloads}] |  add | to_entries[] | select(.value == 0) | .key | select(contains("SNAPSHOT"))'

Это упрощает весь процесс, воздействуя на URL-адрес файла, а не только на имя. Это упрощает последующий вызов DELETE. Также можно удалить вызовы sort и tr.

person Charles Duffy    schedule 16.03.2018
comment
myproject_1.1-SNAPSHOT_0210f77_mc_3.5.0.zip имеет 2 загрузки. - person Freiheit; 16.03.2018
comment
Не второй экземпляр. Посмотрите внимательнее на входной документ. - person Charles Duffy; 16.03.2018
comment
Никакой нормализации не требуется. Я тестирую некоторые входные данные. Моя отладка, мои слишком широкие примеры и мои правки, вероятно, меня дурят. - person Freiheit; 16.03.2018
comment
О, это умно! Мне пришлось убрать флаг -n. Спасибо. Я вижу, как это работает, и я изучил несколько новых команд jq. - person Freiheit; 16.03.2018
comment
@CharlesDuffy - [{a:1},{a:2}]|add НЕ дает {"a":3}. - person peak; 16.03.2018
comment
@peak, ...ааа, ты прав. Я очистил текст ответа в свете этого; вероятно, также потребуется убрать искажения в истории комментариев. - person Charles Duffy; 16.03.2018

Вот решение, которое суммирует значения .download на .name перед тем, как сделать выбор на основе общего количества загрузок:

reduce (.values[] | select(.name | contains("SNAPSHOT"))) as $v
  ({}; .[$v.name] += $v.downloads)
| with_entries(select(.value == 0))
| keys_unsorted[]

Пример:

$ jq -r -f program.jq input.json
myproject_1.1-SNAPSHOT_thanks_for_the_reminder_charles_duffy_mc_3.5.0.zip

p.s.

Что не так с моим оператором select...?

Проблема, которая бросается в глаза, — это часть конвейера непосредственно перед фильтром «выбрать»:

.values | {name: .[].name, downloads: .[].downloads} 

Использование .[] таким образом приводит к формированию декартова произведения, то есть приведенное выше выражение будет генерировать n*n наборов JSON, где n — длина .values. Вы видимо хотели написать:

.values[] | {name: .name, downloads: .downloads} 

который можно сократить до:

.values[] | {name, downloads} 
person peak    schedule 16.03.2018
comment
Спасибо за дополнительное объяснение основной проблемы с моим исходным кодом. - person Freiheit; 17.03.2018