Colon command in shell scripts

· 2 min

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.

Auditing commit messages on GitHub

· 6 min

After reading Simon Willison’s amazing piece on how he builds a feature, I wanted to adopt some of the good practices and incorporate them into my own workflow. One of the highlights of that post was how to kick off a feature work. The process roughly goes like this:

  • Opening a new GitHub issue for the feature in the corresponding repository.
  • Adding a rough description of the feature to the issue.
  • Creating a feature branch off of main/master/trunk. If the feature is trivial or just a doc update, this step can be skipped.
  • Referring to the issue in every commit message as you start working on the feature:
    • Appending #refs <issue-number> to every commit message. This will attach the commit to the concerning issue on the GitHub UI.
    • Appending #closes <issue-number> to the final commit message when the feature is complete.
    • If you need to refer to an issue after it’s closed, you can still do that by appending #refs <issue-number> to the commit message. So a commit message should look similar to Feature foo, refs #120 or Update foo, closes #115. The comma (,) before refs/closes is essential here. I like to enforce it.

This pattern also works for bugfixes without any changes. Here’s an example issue that shows the workflow in action. Plus, I follow a similar pattern to write the blogs on this site as well. This is what a feature issue might look like on GitHub:

To quote or not to quote

· 3 min

My grug brain can never remember the correct semantics of quoting commands and variables in a UNIX shell environment. Every time I work with a shell script or run some commands in a Docker compose file, I’ve to look up how to quote things properly to stop my ivory tower from crashing down. So, I thought I’d list out some of the most common rules that I usually look up all the time.

Returning values from a shell function

· 3 min

TIL that returning a value from a function in bash doesn’t do what I thought it does. Whenever you call a function that’s returning some value, instead of giving you the value, Bash sets the return value of the callee as the status code of the calling command. Consider this example:

#!/usr/bin/bash
# script.sh

return_42() {
    return 42
}

# Call the function and set the return value to a variable.
value=$return_42

# Print the return value.
echo $value

I was expecting this to print out 42 but instead it doesn’t print anything to the console. Turns out, a shell function doesn’t return the value when it encounters the return keyword. Rather, it stops the execution of the function and sets the status code of the last command in the function as the value that the function returns.

When to use 'git pull --rebase'

· 2 min

Whenever your local branch diverges from the remote branch, you can’t directly pull from the remote branch and merge it into the local branch. This can happen when, for example:

  • You checkout from the main branch to work on a feature in a branch named alice.
  • When you’re done, you merge alice into main.
  • After that, if you try to pull the main branch from remote again and the content of the main branch changes by this time, you’ll encounter a merge error.

Reproduce the issue

Create a new branch named alice from main. Run:

Automerge Dependabot PRs on GitHub

· 3 min

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.

Distil git logs attached to a single file

· 2 min

I run git log --oneline to list out the commit logs all the time. It prints out a compact view of the git history. Running the command in this repo gives me this:

d9fad76 Publish blog on safer operator.itemgetter, closes #130
0570997 Merge pull request #129 from rednafi/dependabot/...
6967f73 Bump actions/setup-python from 3 to 4
48c8634 Merge pull request #128 from rednafi/dependabot/pip/mypy-0.961
5b7a7b0 Bump mypy from 0.960 to 0.961

However, there are times when I need to list out the commit logs that only represent the changes made to a particular file. Here’s the command that does exactly that.

Health check a server with 'nohup $(cmd) &'

· 2 min

While working on a project with EdgeDB and FastAPI, I wanted to perform health checks against the FastAPI server in the GitHub CI. This would notify me about the working state of the application. The idea is to:

  • Run the server in the background.
  • Run the commands against the server that’ll denote that the app is in a working state.
  • Perform cleanup.
  • Exit with code 0 if the check is successful, else exit with code 1.

The following shell script demonstrates a similar workflow with a Python HTTP server. This script:

Don't add extensions to shell executables

· 1 min

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:

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

· 1 min

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: