Tucker Chapman

Today I Learned

Generate Random String with CLI

August 03, 2026

TIL a few one-liners for generating random strings.

date +%s | sha512sum | base64 | head -c 24 ; echo

The string is pseudo random and seeded by the current UNIX timestamp. The command should work with most Linux distros and MacOS. If running on Linux a more random command to try uses /dev/urandom and tr.

tr -dc A-Za-z0-9 </dev/urandom | head -c 24; echo

If you need a passphrase you can use the dictionary file to select n random words.

grep "^[[:alpha:]]\{5,8\}$" "/usr/share/dict/words" | sort -R | head -4

Firefox Address Bar Shortcuts

July 23, 2026

TIL The Firefox Address Bar includes shortcuts for all kinds of things. Use special characters to search your history (^), bookmarks (*), and tabs (%). Type the shortcut character before your search to filter the dropdown entries to tab through. Use the @ to search configured websites, history, bookmarks, and tabs (type @bookmarks, @wikipedia, etc.). Add keywords to your bookmarks to create shortcut for your favorite sites (e.g. “wiki” to go to Wikipedia). For the cherry on top, use Ctrl + L (Cmd + L) to focus on the address bar.

Search with the Firefox address bar

JWTs are Base64URL Encoded Strings

July 18, 2026

TIL a JWT (JSON Web Token) is a couple Base64URL encoded strings with a signature. A JWT takes the structure <header>.<payload>.<signature>. The header and payload of a JWT aren’t secret (they’re Base64URL encoded). The magic of JWTs is how the signature is calculated. Calculate the signature by Base64URL encoding the header and payload then hashing with a secret HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret). Modifying the header or payload changes the signature–making the data untrustworthy.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJtZXNzYWdlIjoiSGVsbG8sIFdvcmxkISJ9.
PDCAOVDBAyeZBAm_YIKD_RmoxMCObOqUXbLg9dEp6zM

Introduction: jwt.io

Spec: RFC 7519

Helpful Vim Ex Mode Commands

July 04, 2026

Simple patterns to do useful things with the Ex Mode :global command in Vim

Delete all lines that match a pattern (:help pattern)

:g/pattern/d
:global/pattern/delete

Delete all lines that don’t match a pattern (:help :vglobal)

:g!/pattern/d
:v/pattern/d
:vglobal/pattern/delete

Remove all trailing whitespace with :substitute

:%s/\s\+$//e

Find more info by running :help :global and :help :substitute

SQL Simple Pivot Pattern

September 06, 2025

TIL a simple pattern for pivoting data with an SQL query using FILTER (or a CASE WHEN clause if the FILTER keyword isn’t supported by the database). The example input data is from the Chinook Database and is modeled like this:

| InvoiceDate         | BillingCountry | Total |
| ------------------- | -------------- | ----- |
| 2011-02-15 00:00:00 | Belgium        | 1.98  |
| 2009-10-17 00:00:00 | Brazil         | 13.86 |
| 2009-03-05 00:00:00 | USA            | 3.96  |
| 2012-09-05 00:00:00 | Czech Republic | 16.86 |
| 2012-10-27 00:00:00 | India          | 1.98  |
| 2012-08-27 00:00:00 | Hungary        | 3.96  |
| 2011-07-25 00:00:00 | Canada         | 8.91  |
| 2012-06-25 00:00:00 | USA            | 1.98  |
| 2012-02-22 00:00:00 | Poland         | 1.98  |
| 2013-03-05 00:00:00 | Italy          | 8.91  |

A pivot query using FILTER (with a SQLite db)

SELECT strftime('%Y', InvoiceDate) AS year,
 sum(Total) FILTER (WHERE BillingCountry = 'Canada') AS canada_total,
 sum(Total) FILTER (WHERE BillingCountry = 'USA') AS usa_total,
 sum(Total) FILTER (WHERE BillingCountry NOT IN ('Canada', 'USA')) AS rest_total
FROM Invoice
GROUP BY strftime('%Y', InvoiceDate)

or using CASE WHEN

SELECT strftime('%Y', InvoiceDate) AS year,
 sum(CASE WHEN BillingCountry = 'Canada' THEN Total END) AS canada_total,
 sum(CASE WHEN BillingCountry = 'USA' THEN Total END) AS usa_total,
 sum(CASE WHEN BillingCountry NOT IN ('Canada', 'USA') THEN Total END) AS rest_total
FROM Invoice
GROUP BY strftime('%Y', InvoiceDate)

both create this output:

| year | canada_total | usa_total | rest_total |
| ---- | ------------ | --------- | ---------- |
| 2009 | 57.42        | 103.95    | 288.09     |
| 2010 | 76.26        | 102.98    | 302.21     |
| 2011 | 55.44        | 103.01    | 311.13     |
| 2012 | 42.57        | 127.98    | 306.98     |
| 2013 | 72.27        | 85.14     | 293.17     |

Interpolate Environment Variables with Docker Compose

April 11, 2025

TIL you can use environment variables in your docker compose files. I use environment variables with my homelab setup as a simple way to keep secrets out of version control and reduce repeating common file paths for binded volumes. Docker compose even uses a .env file out of the box. Most shell interpolation features work too.

services:
  web:
    image: nginx
    ports:
      - "8080:${NGINX_PORT:-80}"
    environment:
      - NGINX_HOST=${NGINX_HOST:?error}
      - NGINX_PORT=${NGINX_PORT:-80}

Reference: Environment Variables in Compose

Create multiple nvim configs with $NVIM_APPNAME

February 08, 2025

TIL you can save multiple nvim config setups with the $NVIM_APPNAME environment variable. Nvim will look for, or create, the config in the $XDG_CONFIG_HOME/$NVIM_APPNAME directory. Easily switch between the configs with aliases in the startup file for your shell (i.e. .bashrc, .zshrc, etc.).

# $NVIM_APPNAME defaults to nvim
alias avim='NVIM_APPNAME="nvim-astro" nvim'
alias zvim='NVIM_APPNAME="nvim-lazy" nvim'
alias kvim='NVIM_APPNAME="nvim-kickstart" nvim'

Source: $NVIM_APPNAME docs

Exclude Directories using the Find command

January 18, 2024

TIL you can exclude directories when using the find command.

## Exclude the node modules directory when searching with find
find ./ -name "*.css" ! -path "*/node_modules/*"

Source: Stack Overflow

Python Http Server

January 18, 2024

Python comes with a simple http server you can use to serve a directory of files as a web server. I use it all the time to serve code coverage reports and various other static web projects that don’t come with a server. Start the server using this command:

$ python -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Once this is running you can now access the files in the directory with your web browser (at http://localhost:8000).

Bonus: To use a different port

$ python -m http.server 3000
Serving HTTP on :: port 3000 (http://[::]:3000/) ...

Double Bonus: On older systems, or systems without python3 installed, python2 uses a different command

$ python2 -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
$ python2 -m SimpleHTTPServer 9000
Serving HTTP on 0.0.0.0 port 9000 ...

Python Context Manager

September 21, 2023

TIL you can write your own functions to use in a python with statement. These functions are called context managers. A context manager is a simple way to wrap a try/except/finally block in a reusable function.

Writing your own context manager is simple.

from contextlib import contextmanager

@contextmanager
def random_number():
    """ https://xkcd.com/221/ """
    try:
        # do any setup
        yield 4 # Yielded objects can be accessed from the `var` after the `as` keyword
    except:
        # handle any errors
        pass
    finally:
        # Clean up here (e.g. close db connections or open files)
        pass

## Call the context manager like this
with random_number() as n:
    # do something with `n`
    print(n)

PEP-343 has some other context manager examples.