Typing. As a developer you do a ton of it day in and day out. However, most typing is in service of menial tasks that you repeat several times throughout the day. You should be focused on solving problems, not remembering the API to various command line applications or boilerplate language idioms. Wouldn’t it be great if you could automate those tasks? The good news is that nearly 100% of the typing you do is on a computer, which by some kind of miracle is able to be um… programmed. In this post, I’ll share some of my favorite ways to stop doing grunt work and focus on solving client problems.
Shell Scripting
At some point we all learn to get comfortable with using the terminal. Most of us set up a custom prompt and maybe add a few aliases. This is only the tip of the iceberg of functionality that shell scripting provides. You don’t have to become a bash expert because “it works on my machine” is synonomous with “it works” when it comes to bash scripting. This is not an attempt to cover all of bash scripting but I will share some tricks I’ve compiled up over the years. If you wonder what any of these commands do, please refer to http://explainshell.com/, a very handy tool for those not familiar with every single man page .
Colors
Everyone wants pretty colors in their terminal. Colors in the terminal are rendered using cryptic escape codes. Do yourself a favor and make a .colors file in your home directory. In that file, create a variable and a function for each color so you never have to deal with the crazy escape sequences (to render “Hello World” in red text simply use: red "Hello World"):
# Colors
end="\033[0m"
red="\033[0;31m"
redb="\033[1;31m"
function red {
echo -e "${red}${1}${end}"
}
function redb {
echo -e "${redb}${1}${end}"
}
You can view a complete example here: https://gist.github.com/daytonn/8677243
The following two functions are handy for testing custom colors and themes:
function colors {
black "black"
blackb "blackb"
white "white"
whiteb "whiteb"
red "red"
redb "redb"
green "green"
greenb "greenb"
yellow "yellow"
yellowb "yellowb"
blue "blue"
blueb "blueb"
purple "purple"
purpleb "purpleb"
lightblue "lightblue"
lightblueb "lightblueb"
}
function colortest {
T='gYw' # The test text
echo -e "\n 40m 41m 42m 43m\
44m 45m 46m 47m";
for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \
'1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \
' 36m' '1;36m' ' 37m' '1;37m';
do FG=${FGs// /}
echo -en " $FGs \033[$FG $T "
for BG in 40m 41m 42m 43m 44m 45m 46m 47m;
do echo -en "$EINS \033[$FG\033[$BG $T \033[0m";
done
echo;
done
echo
}
Aliases
If you don’t like typing repetitive commands then you love aliases. Here’s a few aliases I find come in handy on a daily basis:
# Editing the environment alias brc="$EDITOR ~/.bashrc" alias bp='$EDITOR ~/.bash_profile' alias bfn='$EDITOR ~/.bash_functions' alias als='$EDITOR ~/.aliases' alias reload='exec $SHELL' # Directory navigation alias ~="cd ~" alias home="cd ~" alias ..="cd ../" alias ...="cd ../.." alias ....="cd ../../.." alias lst="ls -alh" alias d="cd ~/Development" alias dt='cd ~/Desktop' alias dl="cd ~/Downloads" alias db='cd ~/Dropbox' # Force colors alias grep="grep --color=auto" alias egrep="egrep --color=auto" alias fgrep="fgrep --color=auto"
These aliases are handy for anyone who spends any amount of time on the command line. My favorites are the .. navigation aliases and lst, I must type those a hundred times a day. Also notice reload, brc, and aliases which provide quick access to editing my environment. I encourage you to also make aliases more specific to your own workflow to cutdown on the repetition. For example some handy rails aliases I have are:
alias rs="rails server" alias rc="rails console" alias seed="rake db:seed" alias create="rake db:create" alias drop="rake db:drop"
The point is you should spend less time typing these commands and more time just running them.
Bash Functions
This is where it gets fun. Aliases are great but they basically just shorten up an existing workflow, what about creating custom scripts tailored to your specific workflow?
Startup
Every morning, when I sit down at my desk, there’s a slew of applications I need to boot up to get started. Even with Alfred there’s a lot of repetitive typing. The open command on OSX let’s you open any file with a given application. Sounds perfect for a startup function:
function startup {
open -a Flowdock
open -a localhost:3000 -a "Google Chrome"
open -a Airmail
open -a iTunes
open -a "Sublime Text"
open -a Messages
open -a nValt
}
Git push/pull
Another task I do often during the day is to push and pull code from github. Sometimes I’m in a branch; sometimes I’m not. I don’t want to bother with this minutae, I want to push and pull with abandon. These functions let me do just that:
function pull {
if [ -n "$1" ]; then
git pull origin "$1"
else
branch=$(git rev-parse --abbrev-ref HEAD)
git pull origin $branch
fi
}
function push {
if [ -n "$1" ]; then
git push origin "$1"
else
branch=$(git rev-parse --abbrev-ref HEAD)
git push origin $branch
fi
}
Project root
Often enough, I’ve cd’d into a nested subdirectory of the current project I’m working on. I have .., ..., and .... aliases that can take me up 1, 2, and 3 directories respectively. However, why should I have to think about how many folders deep I am. Turns out, for me there’s an easy way to determine if I’m in the root folder of my project: the .git directory. The following pr function simply winds up directories until it encounters the .git directory:
function pr {
local dir="$PWD"
until [[ -z "$dir" ]]; do
if [ -d ./.git ]; then
break
else
cd ..
fi
dir="${dir%/*}"
done
}
Simple server
I found a handy tip on the internet somewhere that mentions using python’s simple http server to serve files over http on the fly. Since I’m not a python programmer and am likely to forget how to use this command, I created a function to run it on a default port with an option to change the port:
function server {
if [ -n "$1" ]; then
python -m SimpleHTTPServer "$1"
else
python -m SimpleHTTPServer 3000
fi
}
Simple Mountain Lion notifications
Once you start writing custom functions and scripts, you may find you’d like to pop up a notification using OSX’s notification system to let you know a script has completed a task. After a trip to google, I came up with this handy little function:
function notify {
if [[ -z "$1" ]]; then
echo "You must provide a message"
echo "Usage: notify 'Something happened' [myscript.sh]"
else
local title="Notification"
if [[ -n "$2" ]]; then
title="$2"
fi
osascript -e "display notification \"${1}\" with title \"${title}\""
fi
}
This function is a thin wrapper around an applescript that shows a notification.
Monkey Patching CLIs
At one point or another every developer (or is it just me) has wished a command line app did this or that automatically or had some other interface. The good news is that you can bash these apps right into shape by monkey patching them. Here are just a few of my favorite monkey patches:
Brewing your own Homebrew
I use homebrew to install applications, but there’s a few things that really annoy me about it. Homebrew makes you run the update command to get the latest formulae for installing packages. This means that when I type install, if I haven’t updated recently, I could be installing an outdated package. In my opinion, install should automatically update. Bash functions to the rescue! The following is a function that “monkey patches” the brew command and updates whenever I do an install:
function brew {
brew_cmd=`which brew`
if [ "$1" == "install" ] || [ "$1" == "upgrade" ]; then
$brew_cmd update
$brew_cmd "$@"
$brew_cmd cleanup
else
$brew_cmd "$@"
fi
}
If you’re not familiar with bash functions, there’s a lot going on here. Let’s walk through the function. First, since we’re clobbering the brew application name, we need to grab a reference to the actual brew application. To do this we set the output of which brew to a local variable. This gives us the full path to the brew executable (/usr/local/bin/brew) so we can call the brew command and not get the function we’re currently writing.
Then we check if the first argument to brew was either “install” or “upgrade”, since these are the only sub-commands we want to wrap in the update process. If we’re updating or installing, simply call brew update ($brew_cmd update) first.
The next line looks a little strange but the $@ variable is a magic bash variable that represents all the arguments passed to the function. This allows us to pass through all the arguments that we originally passed to brew ($brew_cmd "$@").
After that, I think it’s a good idea to run cleanup since I don’t usually end up in the Cellar looking for heirlooms so I do a brew cleanup ($brew_cmd cleanup).
The else branch is simple. We’re not updating or installing an application so we just pass all the arguments through to the original command. ZSH users will be screaming at the screen by now because ZSH is a shell that’s tailor made for this kind of command patching but if you’re using ZSH, you already now this (or you are a hipster and just use it because all the cool kids use it).
Automatic Bundler binstubs
We’ll not get into the ruby version manager war but suffice to say I’ve settled on rbenv. Though there’s some gaps in the whole “let bundler manage your gems” theory. I can’t abide prefixing every command with bundle exec. Thankfully there is binstubs which aleviates this headache. But now I have a new headache of remembering to generate the binstubs. Nope, I’m gonna bash it:
function bundle {
bundler_cmd=`which bundle`
if [ -z "$1" ] || [ "$1" == "install" ]; then
if [ ! -d .bundle/bin ]; then
$bundler_cmd --binstubs .bundle/bin
else
$bundler_cmd
fi
else
$bundler_cmd "$@"
fi
}
This function should look familiar, we’re doing the same command capture from the brew patch. First, we check if we’re calling bundle with no arguments (same as bundle install) or if the first argument is install. If so, we’re going to check if we’ve already generated binstubs by looking for the .bundle/bin directory. If this directory does not exist, we need to append --binstubs .bundle/ to the install command. If the folder exists it just bundle business as usual. If we’re not installing, we just pass the arguments through to the bundle command.
If passed an argument, these functions will push/pull on the origin remote with the given branch name. If no argument is passed it will simply push/pull to/from the current branch on origin.
File creation made simple
If you’ve ever used mkdir -p you’ve probably wished you could do that with the touch command. How nice would it be to create a file in a nested directory structure that does not yet exist. This handy little function named tickle is just the thing:
function tickle {
if [ -n "$1" ] && [ ! -f "$1" ]; then
path=$(dirname $1)
file=$(basename $1)
[ ! -d "$path" ] && mkdir -p $path
/usr/bin/touch "$1"
else
echo "tickle will mkdir -p and touch the file at the end of the path"
echo
echo "Usage:"
echo " tickle path/to/filename.ext"
fi
}
Use the function like so: tickle path/to/new/file.txt. This will create nested directories if they do not exist and then create a file at the end of the path. Pretty sweet huh? Let’s take that to the next level by “tickling” a file when you call touch -p path/to/file to mimick the mkdir -p dirname command:
function touch {
if [ "$1" == "-p" ];
if [ -n "$2" ]; then
tickle "$2"
else
echo "Usage:"
echo " touch -p /non/existent/path/to/filename.ext"
fi
else
/usr/bin/touch "$@"
fi
}
This “monkey patches” the touch command to accept a -p flag which will create a path to the file being touched. Nice now we don’t have to mkdir -p some/nested/path && touch some/nested/path/file.txt to create a new file within a directory.
Go forth and Bash things!
I hope you find some of these aliases and functions helpful in your workflow. More than that I hope I’ve sparked a few automation ideas to customize your workflow. Keep in mind that your environment is YOUR environment. If you keep forgetting common arguments to commands or want custom functionality, crack open your favorite text editor and bash it. You can even look through your history to find great candidates for automation. When you’re 90 your wrists will thank me.
DevMynd is chicago custom software development company with practice areas in digital strategy, human-centered design, UI/UX, and web application and custom mobile development.