DRY: Maintaining a Consistent Bash and Zsh configuration

I use both Bash and ZSH as my shells; I want to keep their configurations as similar as possible, avoid duplicating configuration, and still be able to take advantage of the extras that ZSH offers.

I do this via a ~/.config/shells directory.

My ~/.bashrc sets all Bash-specific items; in my case, this is mostly setting the prompt and command completion. The end of the file sources .config/shells/common_rc, which has most of my configuration:

if [ -f $HOME/.config/shells/common_rc ]; then
    . $HOME/.config/shells/common_rc

ZSH allows you to specify the directory that contains your configuration with a ~/.zshenv file:

export ZDOTDIR="$HOME/.config/shells"

~/.config/shells/.zshrc (note that you'll still have to hide the ZSH files) contains the same inclusion of common_rc. It also sets all ZSH-specific configuration (completion, plugin installation, etc.).

common_rc sets my PATH and other environment preparation. It also sources ~/.config/shells/shell_functions and ~/.config/shells/bash_aliases (the latter existed before using ZSH, and I never renamed the file).

ZSH distinguishes between different kinds of aliases:

  • Normal command aliases must be the beginning of a command; alias ls='ls -l' would use the alias for ls | less but not for sudo ls.
  • Suffix aliases (alias -s) set the program to open files with a specific extension. I don't use these.
  • Global aliases (alias -g) work anywhere on the command-line.

We need a way to pass the "-g" flag to some of our ZSH alias commands, but never to Bash. The method for doing this reliably will depend on your OS(es) and shell(s). As far as I'm aware, every well-known shell but csh will always let you query the $0 variable for its name.

On Linux we can do this via:

# Or something like `if [ "$0" = '/usr/bin/zsh' ]`
if [ "$(cat /proc/$$/comm)" = 'zsh' ]; then

## Most command aliases are simply:

alias diffr='diff -rp'
alias recent='ls -1t | tail -n +2 | head -n'

## If we need a global alias in ZSH, we just add the option:

alias $GLOBAL mv='mv -v'
alias $GLOBAL grep='grep --color=auto'

ZSH's global aliases are more powerful than this; you can do things like alias g='| grep' or alias then=' && '. Bash will accept these too, but attempting to use one will fail. I've never used non-Bash compatible aliases, as it would annoy me to be unable to use an alias on one shell or the other.