Have you noticed that, as an engineer, in every tech organization/team/project there are activities you repeat on a daily basis, often multiple times per day?
These activities might look like:
- Switching between different installed versions of
nodeorjava - Running a long build command like
mvn clean install -DredirectTestOutputToFile - Listing S3 buckets on
dev/stage/prodAWS accounts - Populating local database with test data from Staging environment
- Running multiple commands chained with
&&from the terminal
Although necessary, these activities can drain significant amounts of our daily energy, especially if they take a long time or are repeated multiple times per day.
How many times do we find ourselves searching Slack/Teams history for a build command that someone sent us, which requires switching from wherever we were at the time to Slack, searching, sifting through answers, copying it and coming back to run it?
Those who work with AWS from the terminal know all too well that however powerful aws cli might be, it comes with a verbosity tax. It allows us to perform almost every action on AWS from the terminal instead of navigating the UI, which is fantastic, but we also need to type out commands like this to e.g. switch between environments:
eval $(aws configure export-credentials --profile dev --format env) # for development environment
eval $(aws configure export-credentials --profile stage --format env) # for staging environment
eval $(aws configure export-credentials --profile prod --format env) # for production environment
Wouldn’t it be great if the command would be just… dev instead? You just type dev, hit ⏎ Enter and it performs this for us
eval $(aws configure export-credentials --profile dev --format env)
And the same way for stage and prod - this would be easy to remember and satisfying to run thanks to its simplicity.
As it turns out, with a little help from shell scripting, we can do just that!
Aliases
Terminal shell (like bash, sh, zsh) supports aliases and functions .
An alias allows us to give a short name to a single-line command:
alias dev='eval $(aws configure export-credentials --profile dev --format env)'
and then invoke it in terminal by its new, short name:
dev
# command 'eval $(aws configure export-credentials --profile dev --format env)' runs
Alias can be as short as one character, which is handy for commands that we run frequently, such as a project build command:
alias m='mvn clean install -DredirectTestOutputToFile'
m # command runs
Functions
Aliases are limited to a single line, and sometimes we want to run multiple commands as a single “step”.
Suppose we are testing out the latest release which is packaged as a .tar.gz archive, and we need to download it, extract it, and run a script inside it, like:
wget -O latest.tar.gz https://example.com/latest.tar.gz
tar -xzf latest.tar.gz
./latest/run.sh
In this case, we can wrap these commands in a function:
check() {
wget -O latest.tar.gz https://example.com/latest.tar.gz
tar -xzf latest.tar.gz
./latest/run.sh
}
and call it in terminal with
check
# all of the commands run
For anyone working in a microservice codebase, chances are the git checkout main will fail in some repositories because the branch is called master or vice versa. However, it will work as expected in another repository, depending on when it was created and what convention was followed at the time. And sometimes there are both branches, and you usually decide by which one has more commits.
It would be great to call e.g. master hit ⏎ Enter and have the master or main checked out regardless of the actual branch name. Even better, if there are both branches, automatically pick the one with more commits. We can achieve that with the following function:
master() {
master_exists=$(git rev-parse --verify master 2>/dev/null)
main_exists=$(git rev-parse --verify main 2>/dev/null)
if [ -n "$master_exists" ] && [ -n "$main_exists" ]; then
master_count=$(git rev-list --count master)
main_count=$(git rev-list --count main)
if [ "$master_count" -gt "$main_count" ]; then
git checkout master
else
git checkout main
fi
elif [ -n "$master_exists" ]; then
git checkout master
else
git checkout main
fi
}
Saving aliases and functions for later
Defining aliases and functions in the terminal will make them available only for that terminal session, similar to using a REPL. To make them available permanently, we can add them to a /home/user/.bashrc (where user is your username on Linux), so they survive a terminal restart.
.bashrc is a script that is automatically read (“sourced”) when an interactive terminal starts, and all the functions and aliases in it become available to you.
Depending on the OS/shell, the name and path of this file can differ, but the intention is the same. For zsh on macOS it will be /Users/user/.zshrc.
Additionally, because the .bashrc is sourced at the start of the session, there is autocomplete for aliases and function names!
But I don’t know shell scripting!
You might be thinking “That’s all fine, but I don’t even know enough shell scripting to write those commands in the first place”. There’s good news:
- Most of the time, the functions would be thin wrappers around the commands you are already running manually
- LLMs can help write those for you and the gaps you might need to fill shouldn’t be too complicated, even if you’re not familiar with shell scripting
- Due to their nature, these functions/aliases are written once and run often multiple times per day, so it pays off to put some effort up front, and it will continue paying dividends down the road
Chances are that whenever you need to do some task for which you already have an automation in the form of a shell alias/function, you will feel the joy when typing out a simple command. Not having to remember some flow for a particular task and saving yourself a fair amount of focus and daily energy for the day is a gift that keeps on giving.