Read XKCD in the terminal with some bash magic

XKCD is probably the most popular webcomic with Devs. It only seems right that you can read it from the comfort of your terminal via a xkcd command. How can we pull that off?

I use Kitty as my terminal of choice. It is fast, feature-rich, and is cross-platform (Linux and macOS). The real game-changer is that it is one of the few terminal emulators that uses GPU rendering, and has its own graphics protocol for displaying images. So, out of the box Kitty can show images (with a caveat), why not put this capability to use?

TLDR, the script

For a polished version, you can check out the https://github.com/robole/xkcd GitHub repository. It also shows the subtitle for the comic, like in the screenshot below.

xkcd command that shows the latest comic and shows the subtitle

Here is the short version that just shows the image:

Bash
#!/usr/bin/env bash

# Save this file as "xkcd"

URL=https://xkcd.com/

if [ $# -gt 0 ] && [ $1 = "-r" ]
then
URL=https://c.xkcd.com/random/comic
fi

img=$(curl -s -L $URL |
grep 'href= "https://imgs.xkcd.com/comics.*' |
cut -d'>' -f2 |
cut -d'<' -f1)


kitty +kitten icat $img

Run xkcd to fetch the latest comic, or xkcd -r for a random selection.

Read on if you’d like to understand the finer details! I discuss how to do this using web scraping, however I found out later that there is a simple web API. I show how to use that instead in the alternative method section.

Displaying images in the terminal

Kitty shows images via the icat kitten which is included in the default installation. A kitten is a python script to extend the functionality of Kitty.

To display an image named img.png, you can run the following command in Kitty:

Bash
kitty +kitten icat img.png

There is a caveat that ImageMagick must be installed for icat to work. I had it installed already, so everything worked first time for me! It supports all image types supported by ImageMagick.

I believe iTerm2 has similar functionality with its imgcat script.

If you want to use another terminal emulator, maybe you can use a python library called Überzug to achieve the same outcome. Or you could pass the image off to one of the image viewer utilities, feh and sxiv, but they will always open a new terminal window AFAIK.

Personally, I prefer to have the comic shown as an image in the same terminal window, and Kitty does exactly that. So, it was a no-brainer for me!

Scripting time!

You should not need to install anything else if you have a *nix machine or have cygwin installed on Windows (its installed with Git usually). With the GNU coreutils: curl, grep, and cut, we should be able to get the latest webcomic from the XKCD homepage.

xkcd homepage

It would be nice to be able to read a random XKCD also. Is there a way to do this from the XKCD website?

We are in luck because the XKCD website has a Random page where they fetch a random comic from their archive for you! So, we should be the able to do the same thing with the random page and home page!

To grab the homepage, we use curl with the “-s” flag (to run in silent mode - so there is no progress meter or error messages):

Bash
curl -s https://xkcd.com/

We can use grep to pick out the URL of the image of the webcomic. The webpage is quite short, so we won’t have to write anything too gnarly. We just want to uniquely identify the image URL with the simplest regex (regular expression) possible. On inspection of the HTML, it looks like this snippet is the easiest to target:

HTML
Image URL (for hotlinking/embedding):
<a href= "https://imgs.xkcd.com/comics/rounding.png">https://imgs.xkcd.com/comics/rounding.png</a>

There is an extra space after the equals sign for the href attribute (oddly). If we target the href attribute with the beginning of the URL, we will will get this as the only result. The regex 'href= "https://imgs.xkcd.com/comics.*' should do the trick. We pipe the output of the curl command to the grep command and get the snippet we are after.

using grep to find the link in the webpage

Next, we want to get the URL on its own. We can use cut to strip out the unwanted parts of this string using the angled brackets as a delimiter. Using cut -d'>' -f2 on the output gives us “https://imgs.xkcd.com/comics/rounding.png</a”, if we pipe this output to cut -d'<' -f1, we get the URL on its own!

So, as a single line command, we can run the following the latest webcomic:

Bash
kitty +kitten icat $(curl -s https://xkcd.com  | 
grep 'href= "https://imgs.xkcd.com/comics.*"' |
cut -d'>' -f2 |
cut -d'<' -f1)
complete command to fetch XKCD image from homepage

Let’s tidy the script up to make it more readable, and add the ability to choose a random comic with a -r flag.

If there is a “-r” argument passed as the first parameter to the script, we will fetch the page https://c.xkcd.com/random/comic instead of the homepage. Since this URL will redirect you to another page, we need to adjust our curl command to follow this redirect. We can do this by adding the “-L” flag. And that’s it, the rest of the logic is the same for getting the correct image from the fetched page.

This is our final script, which we put in a file named “xkcd”:

Bash
#!/usr/bin/env bash

URL=https://xkcd.com/

if [ $# -gt 0 ] && [ $1 = "-r" ]
then
URL=https://c.xkcd.com/random/comic
fi

img=$(curl -s -L $URL |
grep 'href= "https://imgs.xkcd.com/comics.*' |
cut -d'>' -f2 |
cut -d'<' -f1)


kitty +kitten icat $img

Now, to get a random comic, we can run xkcd -r.

using script to fetch random XKCD comic

Mission accomplished!

Alternative method - Web API

There is a web API! I discovered this after I had written the script! 😅

On the about page of the xkcd website, it says:

Is there an interface for automated systems to access comics and metadata?

Yes. You can get comics through the JSON interface, at URLs like https://xkcd.com/info.0.json (current comic) and https://xkcd.com/614/info.0.json (comic #614).

You could use the jq, a JSON processor CLI app, with this interface to do this in a more elegant way if you wish. This is what a basic version looks like to retrieve the latest comic with a subtitle:

Shell
#!/usr/bin/env bash

# latest comic
json=$(curl -s https://xkcd.com/info.0.json)

imgUrl="$(jq -r .img <<< $json)"
subtitle="$(jq .alt <<< $json)"

echo ""
kitty +kitten icat $imgUrl
echo $subtitle

The code is more readable. For the random functionality, you need to write more code yourself. Actually, it results in a slightly longer overall.

You can find a polished version of this method in the “json” branch of the aforementioned GitHub repository (https://github.com/robole/xkcd/tree/json).

Final word

You got to love the power of bash scripting for this type of task. Our basic functionality can be achieved with a single-line command, and in less than a dozen lines, we have made a tidy script for reading our favourite comic on the command-line. 🤓

Tagged