The Default Shell

Ever so often, people publish blog posts that tout the Z shell as the best thing since sliced bread. I also know two guys who use it on a daily base. Now, as you could imagine, I am a gadgety kind of person, try new things out and usually “optimize” my workflows but up to now, I was not convinced to switch entirely. There are two reasons for my reluctance:

  1. Bash is the default shell on any Unix-like system I encountered so far.
  2. Most of the zsh features I consider helpful, are already implemented in a way with the Bash or can be simply extended with little code.

I don’t want to convince anyone to switch from zsh to Bash with this post but just demonstrate some of the things in my .bashrc that could be considered as zsh builtin alternatives.

Completion

One of the most cited examples of zsh’s superiority is tab completion. The guy in the post gives a peek on what’s possible:

It knows which commands git takes, which hosts are in my hosts file for ssh, which users my system have when I write chmod, available packages to apt-get, etc.

When I read that, I was puzzled, because everything is possible with the /etc/bash_completion script and auxiliary completion scripts in /etc/bash_completion.d/. On debian-based systems, this is enabled out-of-the-box, but in general you would just source the script in your .bashrc like this:

if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
    . /etc/bash_completion
fi

What is nice about the zsh completion though, is its navigation-style choice of different tab-completed options, so instead of cd /usr/share/<tab><tab><tab> ad infinitum you can tab once and navigate much faster between the different completion options.

Prompt

From what I can see, zsh prompts tend to be colorful and almost always overloaded with information. Personally, I just need the absolute minimum: user name, host (especially when connecting to different machines), and the current directory. However, I also like to have the path to the current working directory truncated to maximize the input line. Unfortunately, I have no idea where this piece of kind originates from, but this is what accomplishes it:

function _prompt_workingdir ()
{
    local pwdmaxlen=$(($COLUMNS/5))
    local trunc_symbol="..."
    if [[ $PWD == $HOME* ]]; then
        newPWD="~${PWD#$HOME}"
    else
        newPWD=${PWD}
    fi
    if [ ${#newPWD} -gt $pwdmaxlen ]; then
        local pwdoffset=$(( ${#newPWD} - $pwdmaxlen + 3 ))
        newPWD="${trunc_symbol}${newPWD:$pwdoffset:$pwdmaxlen}"
    fi
    echo $newPWD
}

zsh (or rather the plugin collection oh-my-zsh?) is also capable of visualizing Git status information when inside a Git-versioned directory. This is also possible within Bash using this fine and fast snippet from Jo Liss:

function _git_prompt()
{
    local git_status="`git status --porcelain 2>&1`"
    if ! [[ "$git_status" =~ Not\ a\ git\ repo ]]; then
        if [ -z "$git_status" ]; then
            local ansi=42
        elif [[ "$git_status" == *"??"* ]]; then
            local ansi=43
        else
            local ansi=45
        fi

        local branch=$(__git_ps1 "%s")
        test "$branch" != master || branch=' '

        echo -n '\[\e[0;37;'"$ansi"';1m\]'"$branch"'\[\e[0m\] '
    fi
}

So, I plug these two functions inside my usual prompt like this

PS1="`_git_prompt`"'\[\033[1;30m\]me\[\033[0m\]@\[\033[1;30m\]\h\[\033[0m\]:\[\033[0;33m\]$(_prompt_workingdir)\[\033[0m\] '

and achieve this

Prompt with Git information

So, that is my prompt. Context-dependent information and as simple as possible.

Instead of changing directories with cd1 and tab-completion I usually use the fantastic [autojump][] tool that changes to the directory that matches with what is supplied as arguments. It needs to learn the paths by going in with cd but this initial learning phase pays off relatively soon, especially when changing into deep hierarchies and when confronted with MS-style uppercased paths. For example,

$ cd /usr/share/gtk-doc/html/gtk3

becomes

$ j u gtk3

Lo and behold!, autojump also supports zsh.

1

Okay, I lied. Sometimes, I use it to go into the next child directory. Or cd - into the previous directory. That’s pretty nifty. [autojump]: https://github.com/joelthelion/autojump

Command-line editing

In terms of command-line editing both shells provide similar, powerful modes. By default both use a scheme similar to Emacs, but can also operate with a Vi-like interface. I only recently began to explore command-line editing and my fingers are not yet accustomed to it. Anyway, here are some common strokes:

  • <M-b>/<M-f>: move by word
  • <C-b>/<C-f>: move by character
  • <C-p>/<C-n>: previous/next historic command line
  • <C-a>/<C-e>: go to beginning/end of the line
  • <C-u>/<C-k>: delete from current cursor position to the beginning/end

You should know them not only because they are easier to type than the arrow and page keys, but because they are available to any application that offers a command-line interface based on readline. For example, to rename a tmux window I am much faster with <C-a>,<C-u> than using the backspace key.


So, this is basically my Bash setup with some simple yet powerful enhancements to the stock experience. I am pretty satisfied with this setup but if you have anything that might convince me to switch to zsh, don’t hesitate to tell me.