GitHub是全球最大的程式碼託管平台,許多軟體資源都可以在該網站上取得。有些人如筆者就喜歡除了把程式原始碼上傳到GitHub外,也把已經編譯好的二進制檔案也一併上傳到GitHub替每個倉庫(Repository)所提供的「Release」區,這樣一來不想自行編譯原始碼的使用者就可以直接到「Release」區中找到對應平台已經編譯好的二進制檔案來直接下載使用。



以Flameshot這個開源的跨平台螢幕截圖軟體為例,其GitHub倉庫網址如下:

我們可以在其Release區下,找到各個版本已經官方編譯好的函式庫和重新打包過的原始碼專案。如下圖:

linux-github-latest-release-download

其中,flameshot-12.1.0-1.ubuntu-22.04.amd64.deb 就是給Ubuntu的DEB安裝包,我們可以將其下載下來,就能在Ubuntu上安裝使用。

然而每次都要用網頁瀏覽器開啟GitHub來找到這個頁面來載最新版的檔案好像也挺麻煩的,在懶惰蟲的趨使下,就必須要想個方式能夠直接在Linux環境下,以指令的方式下載到GitHub倉庫上最新發佈出來的檔案。

GitHub提供的 /releases/latest HTTP API

GitHub有提供許多方便的HTTP API,其中的/releases/latest可以協助我們做到這件事。HTTP請求的網址格式如下:

GET https://api.github.com/repos/<user>/<repo>/releases/latest

以剛才提到的Flameshot為例,就是:

GET https://api.github.com/repos/flameshot-org/flameshot/releases/latest

這支API的回傳值大概如以下這樣(已刪減冗長訊息):

{
    "url": "https://api.github.com/repos/flameshot-org/flameshot/releases/71146895",
    "assets_url": "https://api.github.com/repos/flameshot-org/flameshot/releases/71146895/assets",
    "upload_url": "https://uploads.github.com/repos/flameshot-org/flameshot/releases/71146895/assets{?name,label}",
    "html_url": "https://github.com/flameshot-org/flameshot/releases/tag/v12.1.0",
    "id": 71146895,
    "author": {
        "login": "borgmanJeremy",
        "id": 46930769,
        "node_id": "MDQ6VXNlcjQ2OTMwNzY5",
        "avatar_url": "https://avatars.githubusercontent.com/u/46930769?v=4",
        "gravatar_id": "",
        "url": "https://api.github.com/users/borgmanJeremy",
        "html_url": "https://github.com/borgmanJeremy",
        "followers_url": "https://api.github.com/users/borgmanJeremy/followers",
        "following_url": "https://api.github.com/users/borgmanJeremy/following{/other_user}",
        "gists_url": "https://api.github.com/users/borgmanJeremy/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/borgmanJeremy/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/borgmanJeremy/subscriptions",
        "organizations_url": "https://api.github.com/users/borgmanJeremy/orgs",
        "repos_url": "https://api.github.com/users/borgmanJeremy/repos",
        "events_url": "https://api.github.com/users/borgmanJeremy/events{/privacy}",
        "received_events_url": "https://api.github.com/users/borgmanJeremy/received_events",
        "type": "User",
        "site_admin": false
    },
    "node_id": "RE_kwDOBWsOdM4EPZ2P",
    "tag_name": "v12.1.0",
    "target_commitish": "master",
    "name": "v12.1.0",
    "draft": false,
    "prerelease": false,
    "created_at": "2022-07-03T13:42:21Z",
    "published_at": "2022-07-03T18:03:55Z",
    "assets": [
        {
            "url": "https://api.github.com/repos/flameshot-org/flameshot/releases/assets/70446299",
            "id": 70446299,
            "node_id": "RA_kwDOBWsOdM4EMuzb",
            "name": "flameshot-12.1.0-1-lp15.2.x86_64.rpm",
            "label": null,
            "uploader": {
                "login": "borgmanJeremy",
                "id": 46930769,
                "node_id": "MDQ6VXNlcjQ2OTMwNzY5",
                "avatar_url": "https://avatars.githubusercontent.com/u/46930769?v=4",
                "gravatar_id": "",
                "url": "https://api.github.com/users/borgmanJeremy",
                "html_url": "https://github.com/borgmanJeremy",
                "followers_url": "https://api.github.com/users/borgmanJeremy/followers",
                "following_url": "https://api.github.com/users/borgmanJeremy/following{/other_user}",
                "gists_url": "https://api.github.com/users/borgmanJeremy/gists{/gist_id}",
                "starred_url": "https://api.github.com/users/borgmanJeremy/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/borgmanJeremy/subscriptions",
                "organizations_url": "https://api.github.com/users/borgmanJeremy/orgs",
                "repos_url": "https://api.github.com/users/borgmanJeremy/repos",
                "events_url": "https://api.github.com/users/borgmanJeremy/events{/privacy}",
                "received_events_url": "https://api.github.com/users/borgmanJeremy/received_events",
                "type": "User",
                "site_admin": false
            },
            "content_type": "application/x-rpm",
            "state": "uploaded",
            "size": 10698252,
            "download_count": 33235,
            "created_at": "2022-07-03T18:02:23Z",
            "updated_at": "2022-07-03T18:02:25Z",
            "browser_download_url": "https://github.com/flameshot-org/flameshot/releases/download/v12.1.0/flameshot-12.1.0-1-lp15.2.x86_64.rpm"
        },
        {
            "url": "https://api.github.com/repos/flameshot-org/flameshot/releases/assets/70446300",
            "id": 70446300,
            "node_id": "RA_kwDOBWsOdM4EMuzc",
            "name": "flameshot-12.1.0-1-lp15.2.x86_64.rpm.sha256sum",
            "label": null,
            "uploader": {
                "login": "borgmanJeremy",
                "id": 46930769,
                "node_id": "MDQ6VXNlcjQ2OTMwNzY5",
                "avatar_url": "https://avatars.githubusercontent.com/u/46930769?v=4",
                "gravatar_id": "",
                "url": "https://api.github.com/users/borgmanJeremy",
                "html_url": "https://github.com/borgmanJeremy",
                "followers_url": "https://api.github.com/users/borgmanJeremy/followers",
                "following_url": "https://api.github.com/users/borgmanJeremy/following{/other_user}",
                "gists_url": "https://api.github.com/users/borgmanJeremy/gists{/gist_id}",
                "starred_url": "https://api.github.com/users/borgmanJeremy/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/borgmanJeremy/subscriptions",
                "organizations_url": "https://api.github.com/users/borgmanJeremy/orgs",
                "repos_url": "https://api.github.com/users/borgmanJeremy/repos",
                "events_url": "https://api.github.com/users/borgmanJeremy/events{/privacy}",
                "received_events_url": "https://api.github.com/users/borgmanJeremy/received_events",
                "type": "User",
                "site_admin": false
            },
            "content_type": "application/octet-stream",
            "state": "uploaded",
            "size": 147,
            "download_count": 2173,
            "created_at": "2022-07-03T18:02:25Z",
            "updated_at": "2022-07-03T18:02:25Z",
            "browser_download_url": "https://github.com/flameshot-org/flameshot/releases/download/v12.1.0/flameshot-12.1.0-1-lp15.2.x86_64.rpm.sha256sum"
        }
    ],
    "tarball_url": "https://api.github.com/repos/flameshot-org/flameshot/tarball/v12.1.0",
    "zipball_url": "https://api.github.com/repos/flameshot-org/flameshot/zipball/v12.1.0"
}

其中,tarball_urlzipball_url欄位是GitHub替每個Release主動提供的快照下載網址,而assets欄位是GitHub倉庫的擁有者自行在建立Release時所額外加入的檔案。assets欄位中的每個檔案的browser_download_url欄位,正是該檔案的下載網址。

curl指令

我們可以利用Linux發行版中常會內建的curl指令來調用GitHub提供的這支API。如果環境中沒有安裝curl的話,基於Debian的Linux發行版可以使用以下指令來安裝:

sudo apt install curl

紅帽系的Linux發行版則可以使用以下指令來安裝curl

sudo dnf install curl

curl指令的使用方式如下:

curl -fsS https://api.github.com/repos/flameshot-org/flameshot/releases/latest

curl指令的-f參數可以讓curl指令在遇到HTTP錯誤時不繼續取得主體(body);-s參數可以讓curl指令只輸出URL資源的主體(body)資料;-S參數可以在使用-s參數時依然輸出錯誤訊息到標準錯誤(stderr)中。

linux-github-latest-release-download

sed指令

接著我們可以利用Linux作業系統中內建的sed指令來處理curl指令呼叫GitHub的API所回傳的JSON資料。

例如要抓取browser_download_url欄位中的網址,sed指令可以這樣寫:

sed -r -n 's/.*"browser_download_url": *"(.*)".*/\1/p'

sed指令的-r參數可以讓\1發揮作用(也就是正規表示式的群組功能);-n參數可以讓輸出結果只保留/p要輸出的部份(此處就是\1)。

結合curl指令,寫法如下:

curl -fsS https://api.github.com/repos/flameshot-org/flameshot/releases/latest | sed -r -n 's/.*"browser_download_url": *"(.*)".*/\1/p'

linux-github-latest-release-download

如上圖,由於每個Release中可能會有超過一個的額外檔案,因此我們必須要確保這些網址所連結到的檔案資源都是我們需要下載,不然就得繼續再過濾。這部份可以再使用grep指令來達成,或者我們也可以修改剛才寫好的sed指令,在(.*)內加入更嚴苛的條件,例如:

curl -fsS https://api.github.com/repos/flameshot-org/flameshot/releases/latest | sed -r -n 's/.*"browser_download_url": *"(.*\.ubuntu-22\.04\.amd64\.deb)".*/\1/p'

linux-github-latest-release-download

如上圖,只剩下一個網址了!

再次使用curl指令

有了單獨的下載網址就很容易了,利用Shell的命令替換(Command Substitution)語法,將前面的curlsed指令找出的網址變成參數傳給curl指令使用,讓curl去下載這個網址的資源。

結合後指令寫法如下:

curl -fL "$(curl -fsS https://api.github.com/repos/flameshot-org/flameshot/releases/latest | sed -r -n 's/.*"browser_download_url": *"(.*\.ubuntu-22\.04\.amd64\.deb)".*/\1/p')" -O

curl指令的-L參數可以讓curl允許轉址;-O參數可以將網址資源儲存成檔案,並自動取檔名。

如此一來就能用一行指令把GitHub倉庫上最新發佈的檔案給下載下來了!

linux-github-latest-release-download

如果我們不想讓curl自動取檔名,可以將指令中的-O參數替換成-o參數,並接上檔案路徑,例如:

curl -fL "$(curl -fsS https://api.github.com/repos/flameshot-org/flameshot/releases/latest | sed -r -n 's/.*"browser_download_url": *"(.*\.ubuntu-22\.04\.amd64\.deb)".*/\1/p')" -o flameshot.deb