Bash #3 - PATH-wrapping executables

Posted February 18, 2022 ‐ 3 min read

When fixing up complex systems to our advantage we sometimes need to hook on the intermediate execution of a program. The UNIX PATH environment is the venerable search path for programs available in the environment. In this post I'll discuss how to use to wrap around the execution of a program.

Previous post: #2.

The PATH environment variable is a :-delimited strings of paths that are searched in order for executables. For example, a build system may search for the gcc compiler in PATH.

Let's wrap around gcc. To create our gcc wrapper, we need to decided on a directory on which to place it. The name of the wrapping executable will also be gcc so that it gets picked by PATH lookup. The absolute path of that directory needs to be prepended to PATH for the wrapper to be found. There are various ways to do this. The most common are:

  • Prepend in shell command execution PATH="/directory/to-wrapper:$PATH" <command>, only affecting that command.
  • Modify for the current shell script or interactive shell using export PATH="/diretory/to-wrapper:$PATH"

Our wrapper can be written as such:

#!/bin/bash

# Remove ourselves from $PATH, to prevent infinite
# recursion when the wrapped executable is executed
# by us.
curdir=$(realpath $(dirname ${BASH_SOURCE}))
path_tmp=$(echo $PATH | tr ':' '\n' | \
    awk -v "cwd=${curdir}" '$0!=cwd' | \
    tr '\n' ':')
export PATH=${path_tmp%:}

# Don't let Ctrl-C kill the script
function ctrl_c() { :; }
trap ctrl_c INT

echo "Before execution"

# Execute the original program
$(basename $0) "$@"
e=$? # Save exit status

# Clear out handler
trap - INT

echo "After execution"

exit $e

Notes on what is being done above:

  • Important to clear out the directory in which the wrapper resides from PATH, otherwise we can cause an infinite recursion.
  • Forward the exit status of the original program.
  • Allow logic to be implemented before and following execution.
  • We can control how the original program is executed.
  • We can wrap more than one executable with a single wrapper source.

Demo

$ ls -l bin
total 4
lrwxrwxrwx 1 dan dan   7 Feb 19 09:18 gcc -> wrapper
lrwxrwxrwx 1 dan dan   7 Feb 19 09:18 ls -> wrapper
-rwxrwxr-x 1 dan dan 389 Feb 19 09:15 wrapper

$ export PATH=$(pwd)/bin:$PATH

$ ls bin
Before execution
gcc ls wrapper
After execution

$ gcc non-existant.c
Before execution
gcc: error: non-existant.c: No such file or directory
gcc: fatal error: no input files
compilation terminated.
After execution

Final note

We should be careful about emitting to stdout and stderr by the wrapping code, and may want to even avoid it completely, lest we break assumptions being made by the upper-level scripts that execute the program we are wrapping.


Share this post
Follow author