In which one geek demonstrates their zsh prompt, which (it is hoped) should serve as an example of some of the more complicated aspects of zsh.

A while back, Slashdot had an Ask Slashdot question about shell prompts.  I responded, posting a link to an earlier Kuro5hin post I had made on the subject.  Unlike that Kuro5hin post, the Slashdot post garnered some attention, so I decided to write up how my prompt works.  Since then, I’ve expanded my prompt to take advantage of a lot of zsh stuff, so it should also provide examples to people curious about that sort of thing.

[normal screenshot]

For the impatient, I have a screenshot and the prompt command.  For those wanting more detail, I go into it below.

My shell is zsh.  I got the initial idea for this prompt from the “termwide” prompt in the Bash Prompt HOWTO, but had to modify it to work with zsh.  Since then, I’ve added other features (some of which I’ve also seen in other termwide-based prompts) and taken more advantage of zsh’s facilities.  This prompt is now fairly zsh-specific.

Let’s start at the beginning.

autoload -Uz vcs_info

zsh has support for querying information about the version control system in use in the current directory.  It’s optional, so you need to explicitly tell zsh to load the vcs_info function.

zstyle ':vcs_info:*' enable bzr darcs fossil git hg svn

I limit this to the VCSes I either use regularly or have needed to interact with at some point.  On the (rare) occasion when I work with a new VCS, I check to see if zsh has support for it and, if so, I enable it.

zstyle ':vcs_info:*' formats '%F{4}{%F{2}%s%F{4}:%F{2}%b%u%c%F{4}}─%f'
zstyle ':vcs_info:*' actionformats '%F{4}{%F{2}%s%F{4}:%F{2}%b%u%c%F{4}|%F{2}%a%F{4}}─%f'
zstyle ':vcs_info:*' check-for-changes true
zstyle ':vcs_info:*' stagedstr '+'
zstyle ':vcs_info:*' unstagedstr '!'

This sets up the text that the vcs_info function will use to generate the strings that will go into the prompt.  The first parameter is quite customizable; you can limit a setting to a particular VCS or even a particular repository.  All of my settings are generic, so I use wildcards to match all VCSes and all repositories.

For the format strings, there are a handful of VCS-specific escape sequences; anything not VCS-specific is treated as a standard prompt escape string.  VCS-specific strings I’m using are:

  • %s – The VCS in use; it’s usually git these days, but I like a little reminder if I’m in a Bazaar repo, for instance
  • %b – The name of the current branch
  • %u and %c – Strings that show up if the repo has unstaged and/or staged but uncommitted changes, respectively; the particular strings are determined by the unstagedstr and stagedstr zstyle settings, respectively
  • %a – Describes an action that’s underway, like an in-progress merge

I also use the standard %F and %f strings.  The former sets a particular color while the latter resets all colors.  In the main prompt, I use named variables for each color, but I could only do the same here if the variables were global (and visible in my shell sessions), which I don’t want.  So I use color indices here.

[screenshot showing VCS info]

Here’s the prompt in a git repository with both staged and unstaged changes.

function precmd {

There are a number of prompt variables I want to be set before each prompt, so I change them in the special zsh function named precmd.  This is run before the prompt is displayed.

    vcs_info

The first thing I do is run vcs_info to populate a bunch of variables whose names start with $vcs_info_msg_.  I’ll use them later when assembling the prompt.

    local TERMWIDTH
    (( TERMWIDTH = ${COLUMNS} - 1 ))

local tells zsh that the given variable should be scoped to the current function.  I don’t really like cluttering namespaces.

The (( ... )) construct tells zsh to carry out mathematical operations.  I set the terminal width to one less that the actual width because zsh’s right-hand prompt leaves one character to its right and I want things to line up.

    ###
    # Truncate the path if it's too long. 

    PR_FILLBAR=""
    PR_PWDLEN=""

    local promptsize
    local pwdsize
    if [[ $ZSH_VERSION > 3.0.6 ]]; then
        promptsize=${#${(%):---(%n@%m:%l)---()--}}
        pwdsize=${#${(%):-%~}}
    else
        promptsize=${#${:---($USER@${HOST%%.*}:${TTY#/dev/})---()--}}
        pwdsize=${#$(pwd | perl -pe "s{$HOME}{~}")}
    fi

    if [[ "$promptsize + $pwdsize" -gt $TERMWIDTH ]]; then
	((PR_PWDLEN=$TERMWIDTH - $promptsize))
    else
	PR_FILLBAR="\${(l.(($TERMWIDTH - ($promptsize + $pwdsize)))..${PR_HBAR}.)}"
    fi

This little section is the one that does the width adjusting.  The variables that determine string length are … interesting.  Working from the inside out:

  • ${[varname]:-[string]} returns the value of ${[varname]} normally, but uses [string] instead if ${[varname]} doesn’t exist.  ${:-[string]} is a quick way to do variable-related things to fixed strings.
  • ${([flags])[varname]} uses the flags to alter how the value of the variable is handled.  The percent sign causes prompt expansion to be done on the variable.
  • So ${(%):-%~} does prompt expansion on the literal string “%~”.  (To get just this effect, print -p "%~" would work, too.)
  • ${#[varname]} gives the length of the value of the variable.  zsh appears to handle the pound sign before applying the (%) flag, so I had to nest the (%) flag in order to get things to happen in the right order.

Unfortunately, older versions of zsh didn’t support doing prompt expansion on a variable this way, so I have a fallback to calculate the right values in those cases.  That code might not be incredibly portable; it worked for me on the systems where I needed it to.  I haven’t bothered making it more generic because in most cases, the prompt expansion works and can be counted on to do the right thing.

If adding the current directory would make the prompt wider than the terminal, I just set $PR_PWDLEN to the length it should be and let zsh truncate it.  The actual prompt contains the string %$PW_PWDLEN<...<%~%<<.  When zsh encounters a prompt escape of the form %[num]<[str]<, it will truncate the next part of the prompt until the next %<< or the end of the prompt, whichever comes first.  If truncation is necessary, zsh will remove the beginning of the string to be truncated and add the [str] supplied so that the entire resulting string will be [num] characters long.

(Note that there’s also the %[num]>[str]> sequence, which does the truncation at the end of the string.)

If the combination of invariant prompt and current directory is not wide enough to fill the terminal (this is usually the case), I use another variable flag—one a bit more complicated.  The general form of the left-hand padding flag is ${(l.[len]..[pad1]..[pad2].)[varname]}.  This pads or truncates ${[varname]} so that the result is exactly [len] characters long.  If padding is needed, the shell will use [pad2] once, then as many iterations of [pad1] as are needed.  If you don’t need [pad2], it can be omitted, as can [varname].  I omit both, so the effect is that [pad1] is repeated to a length of [len].  The quoting is done so I can put variables inside the flag statement.

[screenshot showing truncated path]

Here’s a truncated path.

    ###
    # Get APM info. 

    if [[ -x "$(whence -p ibam)" ]]; then
        PR_APM_RESULT=$(ibam --percentbattery)
    elif [[ -x "$(whence -p apm)" ]]; then
        PR_APM_RESULT=$(apm)
    fi

If apm or ibam is present, I’ll need its output.  I could do this in the prompt itself, but calling programs from within the prompt clobbers the value of $? (return code of the last command run), so I call all external programs in precmd(), where they can’t do any damage.

    ###
    # Show virtual environment

    PR_VENV=""
    if [[ -n "$POETRY_ACTIVE" ]]; then
        # I'm not sure how to tell _which_ Poetry venv is running, so that
        # information gets left out.  Sigh. 
        PR_VENV="${PR_VENV}${PR_BLUE}[${PR_GREEN}poetry${PR_BLUE}]${PR_SHIFT_IN}${PR_HBAR}${PR_SHIFT_OUT}"
    fi
    if [[ -n "$CONDA_DEFAULT_ENV" ]]; then
        PR_VENV="${PR_VENV}${PR_BLUE}[${PR_GREEN}conda${PR_BLUE}:${PR_GREEN}${CONDA_DEFAULT_ENV}${PR_BLUE}]${PR_SHIFT_IN}${PR_HBAR}${PR_SHIFT_OUT}"
    fi
    if [[ -n "$VIRTUAL_ENV" ]]; then
        PR_VENV="${PR_VENV}${PR_BLUE}[${PR_GREEN}venv${PR_BLUE}:${PR_GREEN}${VIRTUAL_ENV/#${HOME}/~}${PR_BLUE}]${PR_SHIFT_IN}${PR_HBAR}${PR_SHIFT_OUT}"
    fi

If I’m working in a virtual environment of some sort, I want a cue for that in my prompt.  This sets up the $PR_ENV variable with that cue.  I’ve added in support for the various environments I’ve had to interact with at some point.

For Python, I also have export VIRTUAL_ENV_DISABLE_PROMPT=yes in my .zshenv file to suppress the default changes the venv would otherwise make to my prompt.

[screenshot showing virtual environment info]

Here’s the prompt in a Python venv.

}

And that’s the end of my precmd() function.  Next is preexec(), which is only somewhat related to my prompt, but it’s here anyway.  preexec() is run after you press enter on your command but before the command is run.

setopt extended_glob
preexec () {
    case "$TERM" in
        screen*|tmux*)
            local CMD=${1[(wr)^(<*|*=*|sudo|exec|-*)]}
            if [[ $(hostname -d) == 'work.com' ]]; then
                CMD="$(hostname -s):$CMD"
            fi
            echo -n "\ek$CMD\e\\"
            ;;
    esac
}

I use tmux for a lot of things; I used to use screen, so I have stuff set up for both of them.  My preexec() sets the tmux/screen window title, if I’m running in one of those environments.  I have fun with variable expansion to get what I want in the title, which is the name of the program I’m currently running:

Subscripts for arrays can have flags that affect their behavior, just like variables can.  The ‘(w)’ flag causes a regular variable to be treated as an array, with each element of the array being a whitespace-separated word of the variable’s value.  The ‘(r)’ flag changes the way the index works.  It returns the first element of the array that matches the pattern supplied as the index.  In the pattern (which uses extended globbing), ‘^’ negates it, so I get the first element that doesn’t match.  It skips variable assignment, ‘sudo’, and program options.

Since I use this prompt at work and I might be logged into any one of a number of different systems there, I add the current hostname to the title, too.

[screenshot attached to screen session]

I’ve attached to a running tmux session, which has a window list in the caption.  You can see that the current window, 3, is at a prompt, window 4 is running vim, and 2 is showing something with less.

setprompt () {

The stuff that only needs to be set once is set in a separate function, which I’ve decided to call setprompt().

    ###
    # Need this so the prompt will work. 

    setopt prompt_subst

prompt_subst is not set by default.  It allows variable substitution to take place in the prompt, so I can just change the contents of certain variables without recreating the prompt every time.

    ###
    # Reset some terminal properties. 

    PR_RESET_TERM=""
    if [[ $ZSH_VERSION < 5.1 ]]; then
        # zsh versions before 5.1 don't understand bracketed paste mode,
        # but sometimes a program that _does_ will exit without disabling
        # it.  This ensures it's turned off so it doesn't interfere with
        # pasting into the terminal. 
        PR_RESET_TERM="$PR_RESET_TERM"$'%{\e[?2004l%}'
    fi

This one is pretty much as it says in the comment.  It ensures that if it’s on a version of zsh that doesn’t understand bracketed paste mode, that bracketed paste mode is never on for the prompt.

    ###
    # See if we can use colors. 

    autoload colors zsh/terminfo
    if [[ "$terminfo[colors]" -ge 8 ]]; then
	colors
    fi
    for color in RED GREEN YELLOW BLUE MAGENTA CYAN WHITE; do
        eval PR_$color='%{$terminfo[bold]$fg[${(L)color}]%}'
        eval PR_LIGHT_$color='%{$fg[${(L)color}]%}'
        (( count = $count + 1 ))
    done
    PR_NO_COLOUR="%{$terminfo[sgr0]%}"

This section determines whether or not to use color in the prompt.  I use terminfo codes to be as portable as possible across different terminal types.  And the zsh termcap module provides an associative array for all of the terminfo entries for the current terminal. ‘sgr0’ removes all attributes (bold, underline, etc.) from the text.  ‘bold’ turns on bold text.  ‘colors’ lists the number of colors the current terminal supports.

The colors module provides a function called colors, which creates associative arrays $fg and $bg, which contain the terminal-appropriate ANSI escape codes for setting the forground and background colors, respectively.  Since the arrays are indexed by the lowercase versions of the color names, I use the (L) flag in the parameter expansion for $color to lower-case the value of that variable.  I’ve noticed that colors seems to always populate the arrays regardless of the color support of the terminal, which is why I have the test for the number of supported colors.  I fear it may only do ANSI colors as well, but I have yet to use zsh on a terminal that didn’t use ANSI escapes for setting the colors.

The escape codes are surrounded by %{ and %}.  These are zsh prompt escapes that tell the shell to disregard the contained characters when determining the length of the prompt.  This allows zsh to properly position the cursor.

[screenshot with monochrome prompt]

Prompt with colors removed.

    ###
    # See if we can use extended characters to look nicer. 

    if [[ "$(locale charmap)" = "UTF-8" ]]; then
        PR_SET_CHARSET=""
        PR_SHIFT_IN=""
        PR_SHIFT_OUT=""
        PR_HBAR="─"
        PR_ULCORNER="┌"
        PR_LLCORNER="└"
        PR_LRCORNER="┘"
        PR_URCORNER="┐"
    else
        if [[ $ZSH_VERSION > 3.0.6 ]]; then
            typeset -A altchar
        fi
        set -A altchar ${(s..)terminfo[acsc]}
        PR_SET_CHARSET="%{$terminfo[enacs]%}"
        PR_SHIFT_IN="%{$terminfo[smacs]%}"
        PR_SHIFT_OUT="%{$terminfo[rmacs]%}"
        PR_HBAR=${altchar[q]:--}
        PR_ULCORNER=${altchar[l]:--}
        PR_LLCORNER=${altchar[m]:--}
        PR_LRCORNER=${altchar[j]:--}
        PR_URCORNER=${altchar[k]:--}
    fi

I like using line drawing characters for the prompt.  There are characters for that in UTF-8, but if I’m running in a non-Unicode environment, I’ll use zsh’s termcap support to see if the terminal has an escape sequence to access them.

If the current terminal supports extended characters via an escape sequence, there should be terminfo entries to: a) enable use of the line-drawing character set (enacs), b) enter (smacs) and leave (rmacs) the alternate character set, and c) describe the mappings of line drawing characters (acsc).  The last needs some additional explanation.  The VT100 used the alternate character set with certain lowercase characters to make line-drawing characters.  For instance, “q” was a horizontal line.  The acsc terminfo string is a series of character pairs, with the first in the pair being the vt100 character and the second being the character to get the same result in the current terminal.

A zsh associative array is a natural way to get at the appropriate line drawing characters.  Associative arrays must be declared before use, so that’s what the typeset -A does.  (-A is for defining an associative array.)  set -A [arrayname] assigns values to the array, with keys and value alternating.  (key, value, key, value, etc.)  This is exactly how the entries in terminfo are arranged, but we need spaces between the entries.  The (s.[pattern].) flag causes a variable to be split on every occurence of [pattern].  In my case, there is no pattern, so it matches everywhere, splitting between every character.

${[varname]:-[string]} returns, this time with an actual [varname].  So if the terminal doesn’t support UTF-8 or line drawing characters, the prompt will fall back to simple dashes.

[screenshot showing prompt without ANSI line drawing]

Prompt without line art.

    ###
    # Decide if we need to set titlebar text. 

    # Note: old screen definition was: 
    #
    # PR_TITLEBAR=$'%{\e_screen \005 (\005t) | %(0#.-=[ROOT]=- | .)%n@%m:%~ | ${COLUMNS}x${LINES} | %y\e\\%}'
    #
    # But I've switched to tmux _and_ some systems need a $TERM of
    # "screen" to work, with no other way to tell whether the command is
    # running in screen or tmux.  So I just assume that "screen" really
    # means tmux now.  If I do still need to run screen, I'll need to
    # update screen's hstatus stuff to match the way tmux does it. 
    case $TERM in
        xterm*|mlterm|konsole*|gnome-*|vte*)
            PR_TITLEBAR=$'%{\e]0;%(0#.-=*[ROOT]*=- | .)%n@%m:%~ | ${COLUMNS}x${LINES} | %y\a%}'
            ;;
        screen*|tmux*)
            PR_TITLEBAR=$'%{\e_%(0#.-=[ROOT]=- | .)%n@%m:%~ | ${COLUMNS}x${LINES} | %y\e\\%}'
            ;;
        *)
            PR_TITLEBAR=''
            ;;
    esac

There are several things going on here. Most generally, I’m setting the titlebar text in terminals that support it.  xterm and xterm-alike terminal emulators support a particular escape sequence to set their titlebar contents.  screen uses a different escape sequence to set its hardstatus line.  (And I have my .tmux.conf set up to display the hardstatus in xterm’s title bar—details to be linked.)

I’m also using a special zsh prompt escape.  %([char].[true str].[false str]) is a conditional expression.  If [chr] is an exclamation point, zsh will use [true str] if the current uid is that of root and [false str] otherwise.  Since I want to be root as little as possible, I want zsh to yell at me a lot to remind me if I am.

Finally, the whole string is inside $'...' delimiters.  This causes the string to be parsed like echo -e would do it (so I can put in “\e” instead of literal escape characters).  No other expansion is done, which is also what I want—$COLUMNS and $LINES should only be processed when the prompt is displayed, to deal with changing window sizes.

[screenshot with normal title bar]

Note the title bar.  The “[disp9486]” is added by my window manager, but the rest is from zsh.

[screenshot with titlebar in screen]

Here’s the same thing, but inside tmux.  Here, the additional tmux information is also present in the titlebar.  (There’s also my tmux caption at the bottom, but that’s unrelated to my shell prompt.)

[screenshot with titlebar and user is root]

And finally, here’s the titlebar if I’m root.  (Don’t worry about the other changes; I’ll get to those.)

    ###
    # Decide whether to set a screen title
    case $TERM in
        screen*|tmux*)
            if [[ $(hostname -d) == 'work.com' ]]; then
                PR_STITLE=$'%{\ek%m:zsh\e\\%}'
            else
                PR_STITLE=$'%{\ekzsh\e\\%}'
            fi
            ;;
        *)
            PR_STITLE=''
            ;;
    esac

This is the compliment to my preexec() function.  It sets the screen or tmux window title to “zsh” when sitting at a command prompt.

    ###
    # APM detection

    case "$OSTYPE" in
        linux*)
            if [[ -x "$(whence -p ibam)" ]]; then
                PR_APM='$PR_RED${${PR_APM_RESULT[(f)1]}[(w)-2]}%%(${${PR_APM_RESULT[(f)3]}[(w)-1]})$PR_LIGHT_BLUE:'
            elif [[ -x "$(whence -p apm)" ]]; then
                PR_APM='$PR_RED${PR_APM_RESULT[(w)5,(w)6]/\% /%%}$PR_LIGHT_BLUE:'
            else
                PR_APM=''
            fi
            ;;
        *)
            PR_APM=''
            ;;
    esac

This is for laptops or other computers with batteries.  I assume that if apm or ibam is installed, it’s meant to be used.  These will result in the battery percentage and time left (either to charge or discharge) being displayed in the prompt.

With ibam, that information is on two separate lines.  The percentage is on the first line, so I use ${PR_APM_RESULT[(f)1]} to get just that line.  The (f) flag causes the variable to be treated as an array, with each line being a separate element.  Then ${...[(w)-2]} returns the second-to-last word on that line.  Similar indices retrieve the last word on the third line.

apm is a bit simpler.  Everything is on a single line, so we just grab the fifth and sixth words on that line.  When two indices are separated by a comma, the result is the range of elements between those two, inclusive.  This just happens to be a two element range.  Then substitution is performed to remove the space between them and to double the percent sign, so that prompt expansion witll leave a single, literal percent sign.  ${[varname]/[pattern]/[replacement]} replaces the first occurrence of [pattern] with [replacement].  ${[varname]/[pattern]//[replacement]} replaces every occurrence.

[screenshot with apm info]

Prompt on my laptop, showing 50% battery and just over two hours to finish recharging.  (No, no indication of AC status; that’s planned.  (I usually know which it is.))

    ###
    # Highlight certain machine names. 
    case ${(%):-%m} in
        bastion1|bastion2|bastion3)
            HOST_COLOR=$PR_RED
            ;;
        *)
            HOST_COLOR=$PR_GREEN
            ;;
    esac

At work, I log in to a bastion host and then from there log in to individual other systems.  I use this to highlight the server name when I’m on a bastion host, just to differentiate a little from the other hosts.

[screenshot showing the hostname highlighted]

Highlighting the current host name.

    ###
    # Finally, the prompt. 

    PROMPT='$PR_RESET_TERM$PR_SET_CHARSET$PR_STITLE${(e)PR_TITLEBAR}\
$PR_CYAN$PR_SHIFT_IN$PR_ULCORNER$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\
$PR_GREEN%(0#.%SROOT%s.%n)$PR_GREEN@$HOST_COLOR%m$PR_GREEN:%l\
$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_HBAR${(e)PR_FILLBAR}$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\
$PR_MAGENTA%$PR_PWDLEN<...<%~%<<\
$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_URCORNER$PR_SHIFT_OUT\

$PR_CYAN$PR_SHIFT_IN$PR_LLCORNER$PR_BLUE$PR_HBAR$PR_SHIFT_OUT(\
%(?..$PR_LIGHT_RED%?$PR_BLUE:)\
${(e)PR_APM}$PR_YELLOW%D{%H:%M}\
$PR_LIGHT_BLUE:%(0#.$PR_RED.$PR_WHITE)%#$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\
$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\
$PR_NO_COLOUR '

Here’s where everything is finally assembled.  There are a few things in the prompt itself that I haven’t yet explained completely:

I’m using the (e) flag on many variables to cause them to undergo variable substitution.  (That way, things like $COLUMNS and $LINES are updated automatically.)

There’s an actual newline in the middle of the string to ensure it’s wrapped at the right place.  I put that on a line by itself to make it obvious that it is intentional (not just a forgotten backslash at the end of a line).

The %([char].[true str].[false str]) setup returns in several places.  root gets a slightly more obvious prompt that a normal user, as you can see in the previous screenshot.  Also, using a question mark as the [char] allows me to display the exit code of the previous command.  (The true string would be displayed if the exit code was zero, but I have left that string blank.)

[screenshot showing a return code]

I ran perl -e 'exit 42'.  It dutifully returned 42, which was then shown in the prompt.

The %D{...} construct results in a date string formatted by the contents.  The current hour and minute go on the left side.

    RPROMPT=' $PR_SHIFT_IN$PR_CYAN$PR_HBAR$PR_BLUE$PR_HBAR$PR_SHIFT_OUT${vcs_info_msg_0_}$PR_VENV\
$PR_BLUE($PR_YELLOW%D{%a,%b%d}$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_CYAN$PR_LRCORNER$PR_SHIFT_OUT$PR_NO_COLOUR'

zsh supports a right-hand prompt, too.  I started using it just because it was there, but I’ve come to like it.  Putting stuff on the right frees up space on the left, and the right-hand prompt simply disappears if the command line grows past it.  Thus, I put information that would be useful but that I don’t need all that often on the right.  That includes today’s date and, as you’ve seen above, the current directory’s VCS and virtual environment information.

[screenshot with right prompt removed]

The right-hand prompt has been removed to make room for the command line.

    PS2='$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\
$PR_BLUE$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT(\
$PR_LIGHT_GREEN%_$PR_BLUE)$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT\
$PR_CYAN$PR_SHIFT_IN$PR_HBAR$PR_SHIFT_OUT$PR_NO_COLOUR '

PS2 is the continuation prompt.  I define it mostly so it will match the main one in color.  In it, I use the %_ prompt escape, which shows the reason the continuation prompt is being displayed.

[screenshot showing continuation prompt]

The continuation prompt, in addition to the main one.

There are two other prompts as well (PS2 and PS3), but I don’t bother to set them because I use them rarely, it at all.

}

showprompt

And that’s the end of the prompt-creating function.  Once it’s defined, I call it to actually do the setup.

And that’s it.  My setup’s rather complicated, but it allows me to use the same configuration on every computer I use, leaving it up to the shell to figure out what to do with itself.  I have put a bit of work into putting this thing together, but I have to interact with the command line every day, so I think it’s worth it.