Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save steinwaywhw/a4cd19cda655b8249d908261a62687f8 to your computer and use it in GitHub Desktop.
Save steinwaywhw/a4cd19cda655b8249d908261a62687f8 to your computer and use it in GitHub Desktop.
One Liner to Download the Latest Release from Github Repo
  • Use curl to get the JSON response for the latest release
  • Use grep to find the line containing file URL
  • Use cut and tr to extract the URL
  • Use wget to download it
curl -s https://api.github.com/repos/jgm/pandoc/releases/latest \
| grep "browser_download_url.*deb" \
| cut -d : -f 2,3 \
| tr -d \" \
| wget -qi -
@bluebrown
Copy link

@robertpatrick

Hmm... https://github.com///releases/latest/download/ should work without having to rely on the GitHub REST API, no?

That works only if the publisher doesn't put the version in the artifact file name, which is more often than not the case. The api.github.com, tells you the version and download URL in the response.

@bluebrown
Copy link

My script looks pretty much like this, but you need to be careful with mono repos that publish different artifacts with different tags. Latest is useless on those.

@cinderblock
Copy link

One liner to download and pipe it to tar for extraction directly:

curl -sL $(curl -s https://api.github.com/repos/actions/runner/releases/latest | grep browser_download_url | cut -d\" -f4 | egrep 'linux-arm64-[0-9.]+tar.gz$') | tar zx

@notorand-it
Copy link

notorand-it commented Feb 9, 2023

Why on Earth would you use two different tools for the same task and put curl and wget in the same script/1-liner ?
Why fiddling with grep/tr/cut when the only reliable JSON parsing tool is jq ?

This is from my own stuff (different URL):

wget -q -O /usr/bin $(wget -q -O - 'https://api.github.com/repos/mikefarah/yq/releases/latest' | jq -r '.assets[] | select(.name=="yq_linux_amd64").browser_download_url'')

2 tools is better than 5 IMHO.
Adapting it to other needs is left to the keen reader. ;-)

@joshjohanning
Copy link

joshjohanning commented Feb 15, 2023

a note (for your repositories)

I advise using consistent file-names. I.E. don't include a versioning in the filename.
I know it sounds weird, but bear with me* for a second.
You know the .../releases/latest syntax right? did you know you can also use it to download files as well? (and not just to redirect to the latest release on Github?)
Here, try this URL for example: https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl.exe.
this reduces the need to walk through Github-Releases API entirely! and simplify stuff for users and also web-services that crawls your repository (in-case you have a somewhat popular product :] ). You don't to maintain any kind of backend or domain at all and relink to to the latest binaries (for example: http://youtube-dl.org/downloads/latest/youtube-dl.exe). it is handled by github releases!
you can always include the version in a file named version.txt, or any meta-data, really. keep a consistent file-name here too.
got the idea from https://github.com/yt-dlp/yt-dlp#update

COOL! 👍

This is it

wget https://github.com/aquasecurity/tfsec/releases/latest/download/tfsec-linux-amd64

@NLZ
Copy link

NLZ commented Apr 4, 2023

Exactly what I was hoping to find. 👍

I made a slight adjustment to it for my needs.

$githubLatestReleases = 'https://api.github.com/repos/microsoft/winget-cli/releases/latest'   
$githubLatestRelease = (((Invoke-WebRequest $gitHubLatestReleases) | ConvertFrom-Json).assets.browser_download_url | select-string -Pattern 'appxbundle').Line
Invoke-WebRequest $githubLatestRelease -OutFile 'Microsoft.DesktopAppInstaller.appxbundle'

Powershell can be further simplified with invoke-restmethod's auto-parsing and then exploring the objects in the pipe

Invoke-RestMethod 'https://api.github.com/repos/microsoft/winget-cli/releases/latest' | % assets | ? name -like "*.msixbundle" | % { Invoke-WebRequest $_.browser_download_url -OutFile $_.name }

@bruteforks
Copy link

bruteforks commented Apr 19, 2023

Hmm... https://github.com/<org-name>/<repo-name>/releases/latest/download/<artifact-file-name> should work without having to rely on the GitHub REST API, no?

literally the easiest way i've found. Thank you! example

edit: here's what i ended up with

echo "Check for watchman"
if ! [ -x "$(command -v watchman)" ]; then
echo "downloading and installing latest github release"
wget $(curl -L -s https://api.github.com/repos/facebook/watchman/releases/latest | grep -o -E "https://(.*)watchman-(.*).rpm") && sudo dnf localinstall watchman-*.rpm
watchman version
else
  echo "watchman exists."	
fi

@vavavr00m
Copy link

Nothing for Windows worked for me.

The below Windows batch script sort of worked but it's downloading all latest releases. Anyone knows how to modify it to select only the *-x64.exe release?

  set repo=owner/name
  for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do ( curl -kOL %%B )

@flightlesstux
Copy link

flightlesstux commented Jun 1, 2023

Nothing for Windows worked for me.

The below Windows batch script sort of worked but it's downloading all latest releases. Anyone knows how to modify it to select only the *-x64.exe release?

  set repo=owner/name
  for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do ( curl -kOL %%B )

@vavavr00m could you try this?

set repo=owner/name
for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do (
    set url=%%B
    set "filename=%url:*\=%"
    if "%filename:~-9%"=="-x64.exe" (
        curl -kOL %url%
    )
)

@vavavr00m
Copy link

vavavr00m commented Jun 1, 2023

@flightlesstux Thanks for your response. The %url% stored the -x86.exe release and also the script doesn't also download that file. How do I change it to pick up -x64.exe and download it?

@flightlesstux
Copy link

@vavavr00m You're welcome! This script calls a :download subroutine from the loop. The subroutine sets url and filename variables, then checks the filename and downloads the file if it matches -x64.exe.

@echo off
setlocal enabledelayedexpansion
set repo=owner/name
for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do (
    call :download "%%B"
)
goto :eof

:download
set "url=%~1"
for %%i in (%url%) do set "filename=%%~nxi"
if "%filename:~-9%"=="-x64.exe" (
    curl -kOL %url%
)
goto :eof

@vavavr00m
Copy link

@flightlesstux I tried this on PowerToys. It doesn't download the -x64.exe release(s) for me and %url% output is all the browser_download_url from the PowerToys repo but nothing was downloaded:

image

Would it help to say the .bat is getting the links from a JSON? Should the script echo the URL(s) without the double quotes?

test.bat on W10:

 @echo off

 setlocal enabledelayedexpansion
 set repo=microsoft/PowerToys
 for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do (
      call :download "%%B"
 )
 goto :eof

 :download
 set "url=%~1"
 for %%i in (%url%) do set "filename=%%~nxi"
 if "%filename:~-9%"=="-x64.exe" (
     curl -kOL %url%
)
goto :eof

@antofthy
Copy link

antofthy commented Jun 2, 2023

The problem is that what to search for VARIES from repo to repo.
MOST repos seem to use browser_download_url but I have also have seen repos that does not have that entry, but use tarball_url, zipball_url instead.

@si618
Copy link

si618 commented Sep 13, 2023

@fanuch

My hat in the ring.

VER=$(curl --silent -qI https://github.com/bakito/adguardhome-sync/releases/latest | awk -F '/' '/^location/ {print  substr($NF, 1, length($NF)-1)}'); \
wget https://github.com/bakito/adguardhome-sync/releases/download/$VER/adguardhome-sync_${VER#v}_linux_x86_64.tar.gz 

Thanks for the inspo in this thread - really should be easier than this ...

Agreed, and thanks for putting your hat in the ring; it was very close to what I needed 🙇‍♂️

@codelinx
Copy link

codelinx commented Sep 21, 2023

Using jq, i think this should work with most repos and allow you to saerch different values or fields as needed.

wget $(curl -s https://api.github.com/repos/bitwarden/clients/releases/latest  | \
 jq -r '.assets[] | select(.name | contains ("deb")) | .browser_download_url')
  • jq -r raw search
  • select( .name select the field to refine your download
  • | contains ("deb")) search criteria to get the download url
  • . browser_download_url' return string

NOTE: '--raw-output/-r' With this option, if the filter's result is a string then it will be written directly to standard output rather than being formatted as a JSON string with quotes

@notorand-it
Copy link

But why using wget and curl? Why not just wget or curl?

@codelinx
Copy link

But why using wget and curl? Why not just wget or curl?

This is a programmatic discussion for downloading the file(s). You cant wget/curl get the file because of links and internet things. Other issues are that the file name may change, the version, or the file you may need for your OS may change etc.

@codelinx
Copy link

But why using wget and curl? Why not just wget or curl?

you are just trolling.

@notorand-it
Copy link

notorand-it commented Sep 22, 2023

wget $(wget -q -O - https://api.github.com/repos/bitwarden/clients/releases/latest | jq -r '.assets[] | select(.name | contains ("deb")) | .browser_download_url')

I would say this is NOT trolling.
Just like this, IMHO.

@jrichardsz
Copy link

It worked at first attempt :)
Thank you so much !!

@NiceGuyIT
Copy link

dra is making huge strides in simplifying the download process.

dra helps you download release assets more easily:

  • no authentication for public repository (you cannot use gh without authentication)
  • Built-in generation of pattern to select an asset to download (with gh you need to provide glob pattern that you need to create manually).

@panscher
Copy link

@flightlesstux

The download with NotpadPlus x64.exe does not work.

`
@echo off

setlocal enabledelayedexpansion
set repo=notepad-plus-plus/notepad-plus-plus
for /f "tokens=1,* delims=:" %%A in ('curl -ks https://api.github.com/repos/%repo%/releases/latest ^| find "browser_download_url"') do (
call :download "%%B"
)
goto :eof

:download
set "url=%~1"
for %%i in (%url%) do set "filename=%%nxi"
if "%filename:
-9%"==".x64.exe" (
curl -kOL %url%
)
goto :eof
`

@notorand-it
Copy link

I am not unsubscribing because I actually want to see how far we get, and how long this conversation lasts with all these comments that everyone keeps adding. There are hundreds of ways and different languages to achieve the same result. But do we need the rosetta stone with all the programming languages on earth, and all the different approaches, or can we move on, after having received so many comments? OMG.

" different languages"?

The title reads "One Liner to Download the Latest Release from Github Repo".
I would say that the original thing aimed at using a single line command or pipeline.
So I would say it is aimed at a shell (bash/zsh/PowerShell) not any language.
Still on a single line.

IMHO, a few comments actually hit spot.
All the rest is either multi-line scripts (no. of lines > 1) or just not working.

@antofthy
Copy link

antofthy commented Dec 7, 2023

The title reads "One Liner to Download the Latest Release from Github Repo". I would say that the original thing aimed at using a single line command or pipeline. So I would say it is aimed at a shell (bash/zsh/PowerShell) not any language. Still on a single line.
IMHO, a few comments actually hit spot. All the rest is either multi-line scripts (no. of lines > 1) or just not working.

For a specific repository a single line to do the task can be created. BUT for a general repository, well, what is needed is next to impossible to do in one line, or even in a small script. There are always weird exceptions. I have seen many of them!

The general request that was made, was so general, it is also impossible to achieve. No one answer, or one simple line is itself a complete answer. So you get a multitude of answers, each of which will work for specific cases.

"Simplicity has a habit of expanding into catastrophe."
-- Anne McCaffrey, "The Ship who Sang"

In seeking the unattainable, simplicity only gets in the way.
-- Alan J. Perlis

@notorand-it
Copy link

notorand-it commented Dec 12, 2023

Fair.
A general 1-liner, even to a specific git repo, is quite difficult to achieve.
But a 1-liner that can be easily adapted to a number of cases is a different thing and is something much easier to do.
The type of things you can look for here and in any other question-and-answers web sites.

The original 1st post here, was already a Unix shell-based solution, not a question, using 5 different commands, none aimed at JSON parsing and with inexplicably replicated functions between wget and curl. If the output from GitHub were moved to "compact JSON" (no newlines at all) by GitHub itself, then most of those JSON-unaware scripts would stop working.
The original solution, as most of the subsequent replies, has likely been created by merging 2 different sources.

I have shared my (rather limited) knowledge about scripting to get a simple, minimal and reliable 1-liner that can be easily adapted to a large number of cases.
It only uses 2 tools (wget and jq) aimed exactly at their goals (downloading via HTTP and reliably parsing JSON strings).
This is according to the Unix command line art of simplicity, IMHO.

"Life is really simple, but we insist on making it complicated."
-- Confucius

"It is not a daily increase, but a daily decrease. Hack away at the inessentials."
-- Bruce Lee

"If you can't explain it to a six year old, you don't understand it yourself."
-- Albert Einstein

@TheRealMrWicked
Copy link

Here is a solution for Windows, you need to put the repo owner and name, as well as a string to identify the download, the generic version of the command is below.

for /f "tokens=1,* delims=:" %a in ('curl -s https://api.github.com/repos/<Put repo owner and repo name here>/releases/latest ^| findstr "browser_download_url" ^| findstr "<Put identifying string here>"') do (curl -kOL %b)

Example
Putting the repo as notepad-plus-plus/notepad-plus-plus and the identifying string as .x64.exe we get this command:

for /f "tokens=1,* delims=:" %a in ('curl -s https://api.github.com/repos/notepad-plus-plus/notepad-plus-plus/releases/latest ^| findstr "browser_download_url" ^| findstr ".x64.exe"') do (curl -kOL %b)

Which downloads the latest x64 installer of Notepad++ to the current directory.

@Chuckame
Copy link

Chuckame commented Jan 22, 2024

Here is the simplest way of getting the latest version with only curl and basename: Using the Forwarded url by github when accessing /latest:

basename $(curl -Ls -o /dev/null -w %{url_effective} https://github.com/<user>/<repo>/releases/latest)

Here another variant of it with only curl and a pure bash feature:

version=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/<user>/<repo>/releases/latest)
version=${version##*/}

@ivomarino
Copy link

Here is the simplest way of getting the latest version with only curl and basename: Using the Forwarded url by github when accessing /latest:

basename $(curl -Ls -o /dev/null -w %{url_effective} https://github.com/<user>/<repo>/releases/latest)

Here another variant of it with only curl and a pure bash feature:

version=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/<user>/<repo>/releases/latest)
version=${version##*/}

works great

@jessp01
Copy link

jessp01 commented Mar 12, 2024

Using jq to match a release file pattern (modsecurity-v.*.tar.gz$ in this example):

curl -sL https://api.github.com/repos/owasp-modsecurity/ModSecurity/releases/latest| \
jq -r '.assets[] | select(.name? | match("modsecurity-v.*.tar.gz$")) | .browser_download_url'

@healBvdb
Copy link

Another simple command using the fantastic Nushell
http get https://api.github.com/repos/<user>/<repo>/releases/latest | get tag_name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment