A number of UNIX CLI tools use the "subcommand" pattern, meaning the command
takes a subcommand argument separated by whitespace. A prominent example of this
is git
, which uses subcommands like fetch
, push
, pull
, etc.:
# Git invoking the "fetch" subcommand with flags "--all"
$ git fetch --all
These types of commands have a tricky interaction with bash aliases. For
instance, suppose we always want pip install
to be actually be invoked as pip install --user
. At first we might try to alias pip install
:
# XXX: Does not work, alias name cannot contain whitespace.
alias 'pip install=pip install --user'
Whitespace is forbidden in bash alias names, so unfortunately this approach
doesn't work. A common workaround is to come up with some shorter alias name,
like pi
:
# This works, but might be difficult to remember.
alias 'pi=pip install --user'
This is a matter of taste, but I dislike this approach because I find these short aliases hard to remember. They're also annoying when following instructions for things like installing a library, as you need to recognize aliased commands in the instructions and remember to invoke your alias instead.
Fortunately there's a way to solve this issue using Bash functions and the
command
shell builtin. Here's what it looks like:
function pip() {
if [[ "$1" == "install" ]]; then
shift 1
command pip install --user "$@"
else
command pip "$@"
fi
}
The code inspects the first argument, and it it's install
it:
- Uses
shift 1
to pop theinstall
positional argument - Dispatches to
command pip
with the new flag and the modified argument list
In the else case the code just delegates to command pip
with the unaltered
function arguments. Note that prefixing pip
with command
is necessary here:
without command
, this code would invoke itself recursively.
After doing this, we can see that Bash knows about two versions of pip
: the
function we just defined, and the /usr/bin/pip
executable:
# Look up all of the known types of "pip".
$ type -a pip
pip is a function
...function definition here...
pip is /usr/bin/pip
From time to time you might want to not use the --user
flag. You have two
options. The first option is to use command pip install
to ensure you get the
pip
command, not the pip
function. Another alternative is to use the
absolute command path, e.g. /usr/bin/pip install
, although this is a bit more
verbose and requires that you actually know the command's absolute path.
This is a simple example, but the same general technique applies to more complex
commands, such as those that intermix flags and subcommands, or commands like
gcloud
that use subcommands of subcommands.