Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

How to Customize Your Command Prompt

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

Lately, I've been getting this question a lot: "how did you get your terminal to look the way it does?" If you've noticed my terminal and are curious about how I set it up, this is the tutorial for you! Of course, what you learn here will be enough to get you started on creating your own custom command prompt, as well!


Before we get started, I want to make something extremely clear. I'm certainly a command line enthusiast, but I'm not at all a command line pro. I feel at home in a terminal, but I'm a far cry from knowing everything. So here's the deal: I'm going to show you how I've set up my terminal, but that doesn't mean I'll be able to explain every single line of code we'll look at. There are some things here that are the way they are just because that's what works … and I don't always 100% know why.

With that disclaimer out of the way, let's see what we're doing.


Meeting the Finished Product

Here's what my command prompt looks like:

If you're not sure what you're looking at there, let me explain:

  • In the turquoise, we have the name of the computer; in my case, that's mothership. That's followed by a colon.
  • Next, we have the working directory, in a yellow-orange.
  • If we're in a git repository, we have some info on that next. The main thing here is the branch name (master or tilt_in_post_class in the screenshot). Also, if the working directory is clean, that text appears in green; otherwise, it appears in red.
  • Finally, we have a battery indicator. If the ten triangles are all green and filled in, things are powered up. As my battery empties, the triangles will empty and eventually turn red. Of course, if you aren't on a laptop like I am, this won't be as useful to you.

Getting the Environment Ready

Let's now get a few preliminary pieces together, before actually writing some shell scripts.

First, there's the colour scheme. You might recognize it as Ethan Schoonover's Solarized colour scheme. It's pretty amazing, and I've used it on both the terminal and in Vim ever since I discovered it. If you want to use it in the Terminal, you'll have to install the theme. The Terminal in Snow Leopard didn't support xterm-256color, so you'll have to follow some special directions on the Solarized GitHub page to get that working if you're still on that OS.

If you've moved on to Lion, you can just install the .terminal files you'll find in the xterm-256color folder. Once you have those installed (just double-click them), you should be able to select the one you want in the Terminal preferences. Don't forget to set it as the default scheme.

The next thing to know is that I'm not using the default bash shell in my Terminal. Instead, I've switched to zsh, which is basically much bash-compatible, but has a few nice additions, like better tab completion. Here's how to do it: open the Mac system preferences and go to “Users & Groups.” Unlock the pane by clicking the lock at the bottom and entering your password. Then, right-click on your user in the list and choose “Advanced Options.” In the “Login Shell” field, switch from /bin/bash to /bin/zsh. It's that simple.

Fonts

Next step: get the right font. I'm using Inconsolata at 15pt. It's a free monospace font that I love staring at all day long. (Besides using it in the Terminal, I use it in Vim and TextEdit.) You can set your default font from within the Terminal preferences, right where you choose the colour scheme.

Another small thing is the size of your window: Open Terminal Preferences > Settings and click the Window tab; part-way down, you can choose the number of columns and rows you want; I use 130 columns by 30 rows.

Battery

Remember the battery level indicator? Well, that requires a little script from developer Steve Losh; simply copy that into a file and save it as a python file; since ~/bin is in my terminal PATH, I've saved the file to ~/bin/batcharge.py. As he notes, this script will only work on Mac OS X, so if you're running zsh on another system, you'll have to leave this part out.

Zsh

Last, but most certainly not least, there's oh-my-zsh. According to the Github repo, this is just “A handful of functions, auto-complete helpers, and stuff that makes you shout ‘OH MY ZSHELL!'”

Why use it? For me, I decided to give it a try at one point and I've left it installed. If you use the terminal a lot, poke around oh-my-zsh for a bit when you have some time. You might be surprised at what you'll find. Installing oh-my-zsh is fairly simple: just follow the setup instructions in the README; they're pretty straight-forward.

Now we have all the necessary parts in place. We're ready to actually start creating our custom terminal.


Creating the Files

When you installed oh-my-zsh, it was installed to ~/.oh-my-zsh. Pop that open. You'll see two folders of note: themes and templates. Inside templates, you'll find a file called zshrc.zsh-template This is a template for you ~/.zshrc file. If you've customized your terminal before, you'll know that the .bashrc file is where your customizations are stored when you're using a bash shell. The .zshrc is that same thing, except for the zsh shell. So open up that template file; you don't have to know what exactly is going on; after all, there are a lot of comments in the file that might not make sense. One thing here is important to use. Notice the line that says this:

ZSH_THEME="robbyrussell"

That's the name of the theme your terminal is using. Look in the themes folder: you'll see a robbyrussel.zsh-theme file. What we're going to do is create a theme of our own, so you can replace that string with the name of our new file. I've uncreatively called mine ‘doubleend” because it's got into on both sides of the terminal.

Any other customizations you want to make to your zsh environment can be made in this file. If you use the terminal a lot, check out oh-my-zsh's plugins (in the plugins folder): a ton of useful stuff in there.

Don't forget to copy to zshrc.zsh-template to your home directory and rename it to .zshrc before you make your changes. Now, in the themes folder, create a file with the theme name you set in your .zshrc file. Make sure you give it the .zsh-theme extension. We're ready to build our custom theme.


Building the Custom Theme

The most important thing in your theme file is the PROMPT variable. It's contents is your command prompt. To get the idea of this, just start with this in your theme file:

PROMPT='myPrompt=>'

Open a new Terminal window and you should see this:

All right, let's get to work. We're going to have to write several functions, but we'll start with the PROMPT variable. It might not be noticeable when looking at the terminal, but there are actually three lines to my prompt. The first is a blank line, just to give me some breathing room. The second has all the information, and the third has the arrow. That third line is where you actually type the command. So, here's our start:

PROMPT='

$reset_color→ '

Yes, you can do multiline strings that easily in shell scripting. But what's up with $reset_color? That's a variable that oh-my-zsh defines for us; it resets the colour of the output. This requires a short diversion to discuss how we colour different words in the prompt. You see, there's a code—a series of characters—that switch the following text to a colour. Obviously, there's a code for each available colour. Don't worry, there are other variables for the other colours; you don't have to learn the codes. By the time we get to the third line, though, we want to reset that to the default text colour; so, we use the $reset_color variable.

If you're curious about the arrow character, it's the Unicode rightwards arrow (U+2192, →). That's all.

So, now our prompt is looking like this:

Looking svelte. Let's now add the computer name and working directory. This is all for that second line of our PROMPT variable.

$fg[cyan]%m: $fg[yellow]$(get_pwd)

We start by setting the text colour to cyan; it appears that we're getting that colour code from an associative array or hash; while I don't use it, there's a $bg hash which changes the background colour instead of the foreground (text) colour.

After setting the colour, we have %m this outputs the name of the computer. After the colon and space, we switch the text colour to yellow. Next, we use the dollar sign and parens to add the output of the function get_pwd. This will output our current working directory, we a bit of a twist. If I'm in the home directory, I don't want to see /Users/andrew, I want to see ~ instead. So, here's that function:

function get_pwd() {
  echo "${PWD/$HOME/~}"
}

The function shell is pretty straightforward if you've written JavaScript before; identical syntax. I'm not sure where that search-and-replace syntax originated, but that looks pretty similar to the Vim search-and-replace syntax: If PWD includes the text $HOME (a system variable for your home directory), replace it with ~.

Now, here's what's down:

Good! Now comes the tricky part. You see, I want to right-align the git information and the battery indicator. Since there's no way to actually right-align, we have to count the number of characters of text we want, subtract that from the width of the window, and add that spacing. It's pretty hacky, and the code is pretty messy, but it's all I've been able to find that actually works.

Ready? We insert the spacing with a function that I call get_spacing. So add $(get_spacing) to the end of our second line, so it now looks like this:

$fg[cyan]%m: $fg[yellow]$(get_pwd)$(put_spacing)

Now, that function. Of course, here's the shell:

function put_spacing() {

}

There are four parts inside. Here's the first.

local git=$(git_prompt_info)
if [ ${#git} != 0 ]; then
    ((git=${#git} - 10))
else
    git=0
fi

We start by getting the output from the git_prompt_info function and storing it in a local variable, git. Next, if the length of that string is not 0, we reset git so that is now the length of the string minus 10. Otherwise, we reset git to 0. This doesn't seem to make much sense, until you realize what we're trying to do here. We want to find out how many character “slots” the git information takes up. The tricky part is that we're reusing the variable git: first it holds the string, then it holds the number representing the number of characters our git info is long. If git is zero characters long, we set git to 0; if it isn't (meaning we're in a git repository), we set git to the number of characters in the string, minus 10. This is because the string character count includes the colour codes, which aren't actually visible, so they don't take up width. The double parens? Oh, they're used for doing math.

We do the very same thing for the battery output:

local bat=$(battery_charge)
if [ ${#bat} != 0 ]; then
    ((bat = ${#bat} - 18))
else
    bat=0
fi

In the third part, we figure out how much spaces we'll need:

local termwidth
(( termwidth = ${COLUMNS} - 3 - ${#HOST} - ${#$(get_pwd)} - ${bat} - ${git} ))

A bit more math: we start with COLUMNS, which is the number of characters the Terminal is wide. We subtract all the appropriate values (the 3 is for two spaces and a colon), and we end up with the fact that we need termwidth number of spaces between the left and right parts of the prompt.

Now, let's create a string that's termwidth number of spaces long:

local spacing=""
for i in {1..$termwidth}; do
    spacing="${spacing} " 
done
echo $spacing

A simple for-in loop allows us to create the string; then, we return it.

You can't really tell that the whitespace has been added, so I've added some dummy text so you can see that's it's been added.

Next up, the Git info. We add $(git_prompt_info) to the end of prompt line 2; as you know, that's a function call.

$fg[cyan]%m: $fg[yellow]$(get_pwd)$(put_spacing)$(git_prompt_info)

Notice that we don't change the colour before loading the Git info: the function will take care of that, because it depends on the repository status.

And here's the function:

function git_prompt_info() {
  ref=$(git symbolic-ref HEAD 2> /dev/null) || return
  echo "$(parse_git_dirty)$ZSH_THEME_GIT_PROMPT_PREFIX$(current_branch)$ZSH_THEME_GIT_PROMPT_SUFFIX"
}

The first line just checks to see if we're in a Git repository. If we aren't we return. If we are, the next line echos out the right info. Notice two things here: first, we're using two variables: $ZSH_THEME_GIT_PROMPT_PREFIX and $ZSH_THEME_GIT_PROMPT_SUFFIX. I'll show you how these are defined in a second. The other thing is two other functions that are called. These are provided by oh-my-zsh. The current_branch function just returns the current branch. The parse_git_dirty is more interesting, though. If the current branch is dirty (has uncommitted changes), the function will output the $ZSH_THEME_GIT_PROMPT_DIRTY; otherwise it will output $ZSH_THEME_GIT_PROMPT_CLEAN.

I have these four variables defined like so:

ZSH_THEME_GIT_PROMPT_PREFIX="[git:"
ZSH_THEME_GIT_PROMPT_SUFFIX="]$reset_color"
ZSH_THEME_GIT_PROMPT_DIRTY="$fg[red]+"
ZSH_THEME_GIT_PROMPT_CLEAN="$fg[green]"

Based on these variables, a repo on a clean master branch will output [git:master] in green; a dirty master branch will output +[git:master].

And lastly, we call the battery_charge function:

$fg[cyan]%m: $fg[yellow]$(get_pwd)$(put_spacing)$(git_prompt_info) $(battery_charge)

Here's the battery_charge function:

function battery_charge() {
    if [ -e ~/bin/batcharge.py ]
    then
        echo `python ~/bin/batcharge.py`
    else
        echo '';
    fi
}

If the file exists, we run that file and echo the output. Notice that we use the backticks around the running of the file (those aren't single quotes): this allows us to execute a string of code as if it was in the terminal. If the file doesn't exist, we just echo an empty string.

And with that, we're done! Here's what we end with:


Conclusion

Well, that's what my terminal looks like. I've forked the oh-my-zsh project on GitHub and added this theme, so you can find it there. If you're interested in seeing my other dotfiles, I've got them on GitHub too.

However, I'm not done with my command line setup yet. While I haven't made any changes in a while, I'm thinking of including the current user's name (because I use this same theme on my server), and also some RVM info. Also, I'm not sure why I have the word git in there; I guess I originally had a setup that worked with multiple version control systems … Anyway, the point of all this is that this is something you'll continually be tweaking. When I make changes, I'll be sure to push them to GitHub, so you'll be able to see them there.

Let me leave you with several links that you'll find useful in hacking around on the command line:

Have fun!

Advertisement