← 开发笔记

用 curl 和 jq 查询 GitHub 最新发布版本:选择一个下载资产

用 curl 获取 GitHub 最新发布版本,再用 jq 为所需操作系统、CPU 和格式选择恰好一个下载资产。下方脚本遇到 HTTP 错误、无效 JSON、无匹配或重复匹配时会停止;它返回元数据,不下载文件。

releases/latest 端点不会计算数值最大的 Git 标签。发布版本 API 文档区分了普通标签、草稿、预发布版本和已发布版本。

本示例从公开的 adoptium/temurin17-binaries 仓库查询 Linux x64 HotSpot JDK tar.gz 的元数据。它既不需要令牌,也不会实际下载文件。省略仓库参数时使用该默认值;显式传入空参数则会被拒绝。

处理流程

HTTP 失败会在解析 JSON 前终止脚本。有效的发布版本对象必须恰好包含一个匹配资产;选出其 URL 不等于验证下载的文件。

HTTP 失败会在解析 JSON 前终止脚本。有效的发布版本对象必须恰好包含一个匹配资产;选出其 URL 不等于验证下载的文件。

区分 HTTP 失败与 JSON 选择失败

脚本需要 curl、jq 和 POSIX sh。将它保存为 latest-release.sh。

latest-release.sh

#!/bin/sh
set -eu
if [ "$#" -gt 1 ]; then
    printf '%s\n' 'usage: latest-release.sh OWNER/REPO' >&2
    exit 2
fi
repo=${1-adoptium/temurin17-binaries}
case "$repo" in
    */*/*|/*|*/|*[!A-Za-z0-9_./-]*) printf '%s\n' 'usage: latest-release.sh OWNER/REPO' >&2; exit 2 ;;
esac
case "$repo" in
    */*) ;;
    *) printf '%s\n' 'usage: latest-release.sh OWNER/REPO' >&2; exit 2 ;;
esac
api=${GITHUB_API_URL:-https://api.github.com}
response=$(curl --fail --silent --show-error --location --max-time 20 \
    --header 'Accept: application/vnd.github+json' \
    --header 'X-GitHub-Api-Version: 2026-03-10' \
    "$api/repos/$repo/releases/latest") || exit 1
printf '%s' "$response" | jq --slurp --exit-status '
    if length != 1 or (.[0] | type) != "object" then
        error("expected one release object")
    else .[0] end
    | . as $release
    | if (.tag_name | type) != "string" or (.tag_name | length) == 0
         or (.assets | type) != "array" then
        error("missing release tag or assets")
      else . end
    | [.assets[] | select(.name | test("^OpenJDK17U-jdk_x64_linux_hotspot_.*\\.tar\\.gz$"))] as $assets
    | if ($assets | length) != 1 then
        error("expected exactly one Linux x64 JDK tar.gz asset")
      elif ($assets[0].browser_download_url | type) != "string"
           or ($assets[0].browser_download_url | length) == 0 then
        error("missing asset download URL")
      else {tag_name: $release.tag_name, download_url: $assets[0].browser_download_url} end
'
sh latest-release.sh adoptium/temurin17-binaries

成功结果是带有 tag_name 和 download_url 的 JSON 对象。具体值取决于请求时的发布版本。以下示例是展示结构的测试数据,并非对当前最新发布版本的断言:

{"tag_name":"jdk-17.0.8.1+1","download_url":"https://github.com/adoptium/temurin17-binaries/releases/download/example/OpenJDK17U-jdk_x64_linux_hotspot_example.tar.gz"}

curl --fail 首先处理 HTTP 错误。在解析前先保存响应,可避免管道中的最后一条命令掩盖网络失败。参阅 curl 选项。

jq --slurp 将全部输入读入数组。要求恰好一个 JSON 值,也能拒绝空响应。过滤器随后检查发布版本对象、标签和资产数组,只有恰好一个文件名匹配时才输出结果。参阅 jq 手册。

自动化需要处理的结果

条件 结果
响应有效且恰好一个资产匹配 输出 JSON,状态 0。
HTTP403/404 或连接失败 非零状态。
响应体为空或 JSON 格式错误 非零状态。
无匹配资产或存在多个匹配资产 报错,不随意选择。
仓库参数语法无效 输出用法说明,状态 2。

如果 HTTP 请求成功却没有匹配的资源,请先检查发布文件名,再修改 jq 过滤器:本例选择的是 Java 17 的 Linux x64 软件包,而不是所有 JDK 软件包。若需要 Java 21,请使用 Temurin 平台元数据示例。如果仓库或平台值来自 YAML,请先将配置作为数据解析并验证,再将值传给命令。

示例使用本地 HTTP 响应检查了成功、HTTP 错误、无效 JSON、资产缺失和重复匹配。GITHUB_API_URL 默认为公开 API,也可设为实现同一响应约定的测试服务器。

读取公开资源不要求认证。如果扩展为带认证的请求,应避免将令牌写入源码或 Shell 跟踪日志。检查速率限制响应头,并参阅 GitHub 的速率限制文档。

脚本只选择 URL,并不验证安装文件的完整性。安装自动化仍需处理提供方的校验和或签名,以及目标平台。在把标签交给数字版本比较器之前,应检查它是否满足该脚本的输入约定。

下载所选资源后,将文件的 SHA-256 校验和与可信的预期值进行比较。验证器读取本地字节,不下载发布文件,也不验证发布者身份。

讨论