Bash Builtin Override Trick

Posted on Fri 06 November 2015 in Bash

Over the years, I have found this trick useful for various things and wanted to share them.

I just used this again today to help with some error checking (probably because I've been on a Python + TDD kick lately...), and it allowed me to debug & fix an issue rather quickly.

What is a Bash Builtin?

According to tldp.org, bash builtins are:

A builtin is a command contained within the Bash tool set, literally built in.
This is either for performance reasons -- builtins execute faster than external
commands, which usually require forking off [1] a separate process -- or
because a particular builtin needs direct access to the shell internals.

You likely use them everyday without even realizing they are builtins - like echo, test, source, and unset. What's not as well-known is that these can be overridden.

Testing The Return Value

While building a chroot jail, I had 3 separate sections where yum was installing particular packages that I had labelled prep, setup, and main accordingly. The problem was that packages in the main section were failing to install because of dependency resolution errors.

Typical problem, but nothing looked out of the ordinary to me in the output.

So, here's what I ended up doing:

#!/bin/bash

test()
{
    builtin test $1 -eq 0 || {
        echo "[ERROR] $2"
        exit $2
    }
}

# PREP
yum -y install centos rpm-python
test "$?" "Yum installation error (PREP). See above."

# SETUP
yum -y install rpm-build
test "$?" "Yum installation error (SETUP). See above."

# MAIN
yum -y install my-cool-package
test "$?" "Yum installation error (MAIN). See above."

Overriding the test builtin allowed me to abide by the DRY principle by having a single method to test return values. Notice that the overriding is achieved by using the builtin reserved keyword to execute the builtin version of the command you want to use.

After I ran this, it turned out that two packages - one in PREP and the other in SETUP - were unsigned, which was causing the failure. Without these checks in place, it would have taken a very long time for me to comb through the 100s of dependencies coupled with the packages we were trying to install.

Using comments to label sections, and then adding that label to the error message, really helped me quickly narrow down where the real problem was...which was NOT depsolving problems with my-cool-package as I originally thought.

Echo & Logging

Another useful trick is overriding the echo command. Here's what I generally use for logging output with bash scripts:

#!/bin/bash

echo()
{
    builtin echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

echo "Installing packages..."
yum -y install my-cool-package rpm-build

This prints a timestamp for every call to echo.

This trick can likely be extended many ways, but I've found these two use cases to be the most applicable and useful for everyday script writing, debugging, and running.