Flags for discoverable test config in Go

As your test suite grows, you need ways to toggle certain kinds of tests on or off. Maybe you want to enable snapshot tests, skip long-running integration tests, or switch between real services and mocks. In every case, you’re really saying, “Run this test only if X is true.” So where does X come from? I like to rely on Go’s standard tooling so that integration and snapshot tests can live right beside ordinary unit tests. Because I usually run these heavier tests in testcontainers, I don’t always want them running while I’m iterating on a feature or chasing a bug. So I need to enable them in an optional manner. ...

June 28, 2025

Dynamic shell variables

I came across a weird shell syntax today - dynamic shell variables. It lets you dynamically construct and access variable names in Bash scripts, which I haven’t encountered in any of the mainstream languages I juggle for work. In an actual programming language, you’d usually use a hashmap to achieve the same effect, but directly templating variable names is a quirky shell feature that sometimes comes in handy. A primer Dynamic shell variables allow shell scripts to define and access variables based on runtime conditions. Variable indirection (${!var} syntax) lets you reference the value of a variable through another variable. This can be useful for managing environment-specific configurations and function dispatch mechanisms. ...

January 11, 2025

Discovering direnv

I’m not a big fan of shims - code that messes with commands in the shell or prompt. That’s why, aside from occasional dabbling, I tend to eschew tools like asdf or pyenv and just use apt or brew for installs, depending on the OS. Then recently, I saw Hynek extolling direnv: If you’re old-school like me, my .envrc looks like this: uv sync --frozen source .venv/bin/activate The sync ensures there’s always a .venv, so no memory-baking required. ...

October 2, 2024

Bash namerefs for dynamic variable referencing

While going through a script at work today, I came across Bash’s nameref feature. It uses declare -n ref="$1" to set up a variable that allows you to reference another variable by name - kind of like pass-by-reference in C. I’m pretty sure I’ve seen it before, but I probably just skimmed over it. As I dug into the man pages, I realized there’s a gap in my understanding of how variable references actually work in Bash - probably because I never gave it proper attention and just got by cobbling together scripts. ...

September 20, 2024

The *nix install command

TIL about the install command on *nix systems. A quick GitHub search for the term brought up a ton of matches. I’m surprised I just found out about it now. Often, in shell scripts I need to: Create a directory hierarchy Copy a config or binary file to the new directory Set permissions on the file It usually looks like this: # Create directory hierarchy (-p creates parent directories) mkdir -p ~/.config/app # Copy current config to the newly created directory cp conf ~/.config/app/conf # Set the file permission chmod 755 ~/.config/app/conf Turns out, the install command in GNU coreutils can do all that in one line: ...

July 28, 2024

Pesky little scripts

I like writing custom scripts to automate stuff or fix repetitive headaches. Most of them are shell scripts, and a few of them are written in Python. Over the years, I’ve accumulated quite a few of them. I use Git and GNU stow to manage them across different machines, and the dotfile workflow is quite effective. However, as the list of scripts grows larger, invoking them becomes a pain because the tab completion results get cluttered with other system commands. Plus, often I even forget the initials of a script’s name and stare at my terminal while the blinking cursor facepalms at my stupidity. ...

October 29, 2023

Dotfile stewardship for the indolent

I’m one of those people who will sit in front of a computer for hours, fiddling with algorithms or debugging performance issues, yet won’t spend 10 minutes to improve their workflows. While I usually get away with this, every now and then, my inertia slithers back to bite me. The latest episode was me realizing how tedious it is to move config files across multiple devices when I was configuring a new MacBook Air and Mac Mini at the same time. ...

September 27, 2023

Dynamic menu with select statement in Bash

Whenever I need to whip up a quick command line tool, my go-to is usually Python. Python’s CLI solutions tend to be more robust than their Shell counterparts. However, dealing with its portability can sometimes be a hassle, especially when all you want is to distribute a simple script. That’s why while toying around with argparse to create a dynamic menu, I decided to ask ChatGPT if there’s a way to achieve the same using native shell scripting. Delightfully, it introduced me to the dead-simple select command that I probably should’ve known about years ago. But I guess better late than never! Here’s what I was trying to accomplish: ...

April 29, 2023

Simple terminal text formatting with tput

When writing shell scripts, I’d often resort to using hardcoded ANSI escape codes to format text, such as: #!/usr/bin/env bash BOLD="\033[1m" UNBOLD="\033[22m" FG_RED="\033[31m" BG_YELLOW="\033[43m" BG_BLUE="\033[44m" RESET="\033[0m" # Print a message in bold red text on a yellow background. echo -e "${BOLD}${FG_RED}${BG_YELLOW}This is a warning message${RESET}" # Print a message in white text on a blue background. echo -e "${BG_BLUE}This is a debug message${RESET}" This shell snippet above shows how to add text formatting and color to shell script output via ANSI escape codes. It defines a few variables that contain different escape codes for bold, unbold, foreground, and background colors. Then, we echo two log messages with different colors and formatting options. ...

April 23, 2023

Colon command in shell scripts

The colon : command is a shell utility that represents a truthy value. It can be thought of as an alias for the built-in true command. You can test it by opening a shell script and typing a colon on the command line, like this: : If you then inspect the exit code by typing $? on the command line, you’ll see a 0 there, which is exactly what you’d see if you had used the true command. ...

December 23, 2022

Automerge Dependabot PRs on GitHub

Whether I’m trying out a new tool or just prototyping with a familiar stack, I usually create a new project on GitHub and run all the experiments there. Some examples of these are: rubric: linter config initializer for Python exert: declaratively apply converter functions to class attributes hook-slinger: generic service to send, retry, and manage webhooks think-async: exploring cooperative concurrency primitives in Python epilog: container log aggregation with Elasticsearch, Kibana & Filebeat While many of these prototypes become full-fledged projects, most end up being just one-time journies. One common theme among all of these endeavors is that I always include instructions in the readme.md on how to get the project up and running - no matter how small it is. Also, I tend to configure a rudimentary CI pipeline that runs the linters and tests. GitHub Actions and Dependabot make it simple to configure a basic CI workflow. Dependabot keeps the dependencies fresh and makes pull requests automatically when there’s a new version of a dependency used in a project. ...

July 7, 2022

Inspect docstrings with Pydoc

How come I didn’t know about the python -m pydoc command before today! It lets you inspect the docstrings of any modules, classes, functions, or methods in Python. I’m running the commands from a Python 3.10 virtual environment but it’ll work on any Python version. Let’s print out the docstrings of the functools.lru_cache function. Run: python -m pydoc functools.lru_cache This will print the following on the console: Help on function lru_cache in functools: functools.lru_cache = lru_cache(maxsize=128, typed=False) Least-recently-used cache decorator. If *maxsize* is set to None, the LRU features are disabled and the cache can grow without bound. If *typed* is True, arguments of different types will be cached separately. For example, f(3.0) and f(3) will be treated as distinct calls with distinct results. Arguments to the cached function must be hashable. View the cache statistics named tuple (hits, misses, maxsize, currsize) with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. Works for third party tools as well: ...

January 22, 2022

Don't add extensions to shell executables

I was browsing through the source code of Tom Christie’s typesystem library and discovered that the shell scripts of the project don’t have any extensions attached to them. At first, I found it odd, and then it all started to make sense. Executable scripts can be written in any language and the users don’t need to care about that. GitHub uses this scripts-to-rule-them-all pattern successfully to normalize their scripts. According to the pattern, every project should have a folder named scripts with a subset or superset of the following files: ...

November 23, 2021

Use 'command -v' over 'which' to find a program's executable

One thing that came to me as news is that the command which - which is the de-facto tool to find the path of an executable - is not POSIX compliant. The recent Debian which hunt brought it to my attention. The POSIX-compliant way of finding an executable program is command -v, which is usually built into most of the shells. So, instead of doing this: which python3.12 Do this: ...

November 16, 2021