ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Theming Bash

by Shlomi Fish
02/02/2006

I once wrote in my weblog that just as the number of items on an open source project's to-do list always grows or remains constant, so does the number of projects an open source developer is involved in. Add to that the fact that successful open source projects eventually tend to spawn sub-projects, and you'll see that typical eclectic open-source developers have to deal with a large number of projects and sub-projects.

This is not peculiar to open source developers. Often, people working on software in software shops face similar problems, as do non-programmers such as writers or web designers who work on more than one piece simultaneously, or have to maintain older projects.

The only problem is that we normally only have one computer, and work on one shell account. The purpose of shell "themes" is to make it easy to work on several projects inside of one shell.

What Are Shell Themes?

Shell themes are shell presets that, when invoked, customize the shell with various useful commands for working on a specific project. For example, I can type Theme perl/nav-menu, and then gain some shell commands that are useful for working on my navigation menu module. Among other things, it will also automatically change my directory to ~/progs/perl/www/Nav-Menu/trunk/module/, where I work on the module.

Setting Up the Themes' Infrastructure

Before you can write shell themes, you need to implement an infrastructure for creating them. I keep the logic of the themes in one directory, $HOME/conf/Bash/Themes. To set them up, fetch my Bash themes archive and unpack it under $HOME/conf/Bash or wherever is convenient for you.

Then add the following line to your .bashrc:

. $HOME/conf/Bash/Themes/Source-Me.bash

(Replace $HOME/conf/Bash with the directory in which you placed the Bash themes.)

What do these files do? First, the Source-Me.bash file contains:

__themes_dir="$(dirname $BASH_SOURCE[-1])"

function load_common
{
    source "${__themes_dir}/common/$1.bash"
}

function Theme
{
    local filename

    __theme="$1"
    shift;

    filename="${__themes_dir}/themes/$__theme/source.bash"
    test -e "$filename" || { echo "Unknown theme" 1>&2 ; return 1; }
    source "$filename"
}

complete -W "$(cat ${__themes_dir}/list-of-themes.txt)" Theme

The __themes_dir is a global variable that contains the path to the directory containing the themes (as determined by the path of the Source-Me.bash file). The general convention inside of my Bash themes is that the names of private variables or functions begin with two underscores (as inspired by the C and Python conventions).

load_common() is a utility function that simply "sources" a common library from the themes directory. Individual themes use it to include common pieces of code. The Theme() function is more important. It includes the source file of the theme selected as an argument into the current running process.

Finally, the complete -W command enables sure one can use command line completion on the list of themes after the Theme command. So I can, for example type "Theme " (with a space), press Tab twice, and get a list of themes. Alternatively, I can type the beginning of a theme's name and press Tab to complete it.

The themes go inside of the themes sub-directory in separate directories, where the identifier of each theme is the path in which it resides. Each theme contains a source.bash file that the shell sources.

Note that the __theme variable is not a local one, so the user or other scripts can determine the current theme later on.

Another part of the themes' infrastructure distribution is the gen_list.sh file. Run it whenever you add a new theme or remove an old theme. It contains:

#!/bin/bash

(
    find themes -type d -exec test -e {}/source.bash \; -print |
        sed 's!themes/!!' |
        sort
) > list-of-themes.txt

As you can see, gen_list.sh prepares a sorted list of all the sub-directories of the themes directory that contain the source.bash and places them inside of the file list-of-themes.txt. This file is necessary to enable theme completion for the Theme command.

Examples of Theme Contents

What can you do with themes? Plenty.

Pre-Defined Directories

Almost all of my themes have something like this at their beginning:

base="$HOME/progs/perl/www/Nav-Menu/"
trunk="$base/trunk"
this="$trunk/module"
rw_repos_url="svn+ssh://svn.berlios.de/svnroot/repos/web-cpan/nav-menu"
read_repos_url="svn://svn.berlios.de/web-cpan/nav-menu"
test_dir="$trunk/tests/integration/sites-gen"

These are definitions of directories and Subversion URLs. $this is the default directory, but there are other useful directories. To cd there I can type something like cd $this or cd $trunk.

Because of that, the themes end with the command:

cd $this

Why do I keep such declarations in my themes? Otherwise, all of them will clutter the .bashrc file and also require a lot of namespace-collision prevention, which will in turn make them quite long. Putting them in a theme ensures they are short, the same across all themes, and also that they are only present when I need them.

Theme-Specific Environment Variables

Another good use of themes is to define theme-specific environment variables such as the CVSROOT environment variable, extra PATH elements, and also application-specific environment variables, with which you don't want to clutter the .bashrc, or that vary from theme to theme.

Learning the bash Shell

Related Reading

Learning the bash Shell
Unix Shell Programming
By Cameron Newham, Bill Rosenblatt

Sensitive Completion

Another functionality I like to have in my themes is sensitive filename completion. Usually, I work on a Subversion working copy where there are many temporary or backup files that gvim leaves, as well as the .svn directories for Subversion meta-files. In that case, trying to use the default Bash filename completion with gvim is quite daunting, because my completion often finds files in which I have no interest.

To resolve this, I wrote another Bash function:

__gvim_completion()
{
    local cur
    cur="${COMP_WORDS[COMP_CWORD]}"
    COMPREPLY=( $(compgen -f -X '*~' -- "$cur" |
        grep -vE '(^|/)\.(svn|[^/]+\.swp)($|/)' )
        )
}

complete -o filenames -F __gvim_completion gvim

The complete command instructs the shell to use the __gvim_completion() function for the completion of filenames for the arguments following the gvim command. __gvim_completion in turn throws away all of the undesired files. You can refer to the meaning of the various commands and variables from the Bash man page--I won't explain them here. I should just note that it took a lot of experimentation to get it right (some of it, right before I wrote this article).

Useful Theme-Specific Commands

Running ./Build disttest in a Module::Build-based Perl module prepares a fresh distribution and tests it using its defined tests. I discovered that it also tends to leave a directory behind with the tested distribution inside of it. To remedy this, I use:

__dist_name()
{
    (cd "$this" &&
        cat META.yml | grep "^name:" | sed 's/^name: //'
    )
}

__version()
{
    (cd "$this" &&
        cat META.yml | grep "^version:" | sed 's/^version: //'
    )
}

__test_distribution()
{
    (
        cd "$this"
        ./Build disttest
        rm -fr "$(__dist_name)-$(__version)"
    )
}

The function __dist_name() retrieves the name of the distribution from the META.yml file. A proper and more failsafe way to do it would probably be to use Perl and the YAML module, but this way also works. __version() does the same for the version number. These two functions may be useful to other functions as well.

Finally, the __test_distribution() function does the test and then deletes the temporary distribution directory.

Use, Abuse, and the Future of Shell Themes

There are plenty of other uses of themes. Here are ideas I haven't tried much yet!

Reducing .bashrc Bloat

Ian McDonald's Bash Completion source is 8,313 lines and 192,606 bytes long, as of November 25th, 2005. As you can imagine, it takes quite a long time to source it from your .bashrc file. Using Bash themes, however, you can assign completions to the necessary commands within the theme, and avoid the cost of the rest of the completion commands.

I believe the shell initialization file of many command-line users has many common functions, shell variables, and environment variables that only a small number of projects use. You can put them in the appropriate themes and avoid bloating your shell initialization file. It's more maintainable that way, too!

Whence a Theme?

It's probably not worth the trouble to define a new shell theme, if a project you're working on is temporary, or such that you only update it extremely rarely. I'm also sometimes too lazy to define themes for such projects. For example, I did not define a theme for working on this article.

Individual themes and the theme collection as a whole require some maintenance. However, I still believe the time spent on maintaining themes is worth the convenience of having them. Constructing a new tool for convenience is a long-term investment that pays off with a lot of saved time and frustration in the long run--and customizing your working environment is fun and rewarding.

The History and Future of Shell Themes

I first came up with the idea of shell themes a few years ago, and quickly ended up implementing them. At first, most of them were a relatively rudimentary declaration of some variable directory paths and a cd $this command. Shell themes really took off for me when I needed to write a theme for my work on Subversion, and ended up writing many functions for it. Later themes, like the one for work on HTML::Widgets::NavMenu also incorporated some new facilities.

Nevertheless, I still have a feeling that I've only scratched the surface in regards to shell themes. There must be plenty of other exciting features to add. Here I pass the ball to your court and ask you to come up with more exciting shell customizations for your own themes.

For example, one problem I encountered just before preparing this article was that I couldn't pack a certain theme I created for working on a project for a client in the themes distribution, out of confidentiality. I ended up removing it altogether, but I realized that I would like to have several directories in which to search for themes. I haven't implemented it yet.

Happy theming!

Thanks

Thanks to Joshua Varner and chromatic for going over early drafts of this article and giving useful comments and corrections.

Shlomi Fish

is a software professional, who has been experimenting with programming since 1987 and with various UNIX technologies since 1996. He graduated from the Technion with a B.Sc. in Electrical Engineering, and has been heavily involved as a Linux and open source user, developer, and advocate.

His most successful project so far was Freecell Solver, but he also headed several other projects, and contributed to other projects such as Perl 5, Subversion, and the GIMP.


Return to ONLamp.com.

Copyright © 2009 O'Reilly Media, Inc.