A Guided Tour of My Bashrc

Thu, May 11, 2017

So anyone who knows me will know that I love tools that are configured with dotfiles. Generally I find these tools are more powerful and faster than their (generally) graphical counterparts. I love dotfiles so much that I even wrote a tool to help me manage them: [DFM](https://github.com/chasinglogic/dfm). With all that said I’ve spent many years and hours tweaking my dotfiles and after sharing them with my good friend Chas Busenburg he remarked at how much useful stuff was in my bashrc. I had never really thought about it, I see it as piles of hacks and duct tape, but it does have quite a few useful functions and tricks probably unknown to many Bash users / enthusiasts. So it is on that note that I welcome you to the guided tour of my bashrc. If you’re looking for the full version for reference you can find it with the rest of my dotfiles [here](https://github.com/chasinglogic/dotfiles).

Basic options.

  # -*- mode: sh -*-

This simple line makes Emacs read .bashrc in sh-mode. (Basically it makes my text editor use proper syntax highlighting etc.).

  HISTCONTROL=ignoreboth

This makes bash ignore duplicate lines and lines that begin with a space when adding commands to the history. The reason this is useful is if I run `sl` 15 times before I finally get it correct and run `ls` I really don’t need 15 `sl` entries.

  HISTSIZE=2000

`HISTFILESIZE` and `HISTSIZE` basically do the same thing, they tell bash to store X commands in the history. `HISTFILESIZE` is the number of lines to keep and `HISTSIZE` is the number of commands to keep. I’ve never found a situation where these aren’t identical but I’m sure it exists.

  shopt -s histappend

This makes it so bash keeps adding to the History file and only truncates when the `HISTFILESIZE` is reached.

  shopt -s checkwinsize

Bash when in interactive mode keeps two variables which indicate the size of the window. These are `COLS` (width) and `ROWS` (height). Some commands use these to determine how to fill the screen. This option simply says to update these values after every command is run.

  export LANG="en_US.UTF-8"
  export LC_ALL="en_US.UTF-8"

You’re not supposed to have to set both of these but at one time Powerline really hated me on Mac OSX so in a fit of rage I said “FINE SET ALL THE LOCALE VARIABLES!!!!!!!!”. I don’t really remember if this fixed the problem or not but they’ve been here ever since.

  [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

So this is a nifty trick to detect if something is installed and if so do something. The `[ -x /usr/bin/lesspipe ]` says if this is executable (you can run `man if` to see all the available if flags of which `-x` is one.). Here with `&&`, which means to run the next command if the previous succeeds, we are setting up lesspipe. What is lesspipe you say? Good question, I don’t have it installed on this machine and I don’t really remember, I think it makes less not scroll like crazy when you’re piping new input. :D

  [ -x /usr/bin/dircolors ] && eval "alias ls='ls --color'"

This uses the same trick and if `dircolors` is installed makes `ls` default to using the `–color` flag. This is useful because on Mac `ls` doesn’t support the `–color` flag so if we just alias here we will just get errors.

  if [[ -d "/usr/local/go" ]]; then
      export GOROOT="/usr/local/go"
      export PATH="$GOROOT/bin:$PATH"
  fi

So I’m a multi-machine man, I have a Mac for work, Fedora Laptop for my primary personal machine, and an Elementary OS (Ubuntu) desktop. The problem is I always want the latest go compiler. This basically checks if I’ve installed go from the tarball and if so sets the extra environment variables required.

  export GOPATH=$HOME/Code/go
  export GOBIN=$GOPATH/bin
  export CARGOBIN="~/.cargo/bin"
  export RUST_SRC_PATH="$HOME/.multirust/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src"

These are environment variables that need to be set for the Rust and Go programming languages. They really don’t mean anything to bash itself if you use these you’ll know what they do.

  export HMBIN="$HOME/.local/bin"

This is a variable I suggest everyone set up. I keep this directory in the path so as I write little scripts I can just drop them there and have access to them anywhere. It’s super useful and definitely a top ten tip for Bash.

  export EDITOR="emacsclient -t"

To say I’m an Emacs fan would be like calling Hurricane Katrina a swift breeze. That’s not at all relevant to the `$EDITOR` variable however because all this does is let certain command line tools know which text editor you want to use when they need to shill out.

  export FZF_DEFAULT_COMMAND="fnd"

I use the [fzf](https://github.com/junegunn/fzf)tool written by jungegunn pretty extensively throughout my scripts for various purposes but the primary use case for the tool is finding files. Here I set it up to use my personal[fnd](https://github.com/chasinglogic/fnd) tool to find those files.

  if [[ $TERM != "screen-256color" ]]; then
      export TERM="xterm-256color"
  fi

I used to use Tmux a lot, this makes it play nice with terminal colors.

  export PATH="/usr/local/bin:$HMBIN:$GOBIN:$CARGOBIN:$PATH"

For those who don’t know the `$PATH` is where bash looks for commands to run. So when you type `ls` it searches through all folders (in order) in the `$PATH` separated by colons until it finds a directory which contains an executable named `ls`. So here we are adding all of those custom locations we setup to the `$PATH`.

Prompt

Everyone wants different things out of their prompt, some want as little prompt as possible and some want everything going on in their system in their prompt. I tend toward the minimalist but there is some information I want always available.

  ORANGE=$(tput setaf 166)
  RED=$(tput setaf 160)
  VIOLET=$(tput setaf 61)
  BLUE=$(tput setaf 33)
  CYAN=$(tput setaf 37)
  NO_COLOR="\e[0m"

We use these colors to make the prompt pretty. It’s much easier to read `$ORANGE` in the definition than a bunch of tputs, so set them up before hand.

  function parse_git_branch {
      ref=$(git symbolic-ref HEAD 2> /dev/null) || return
      echo "$ "
  }

This is a bash function which either prints the current git branch or nothing if not in a git repo. The fancy `echo "$ "` piece just cuts the “refs/heads/” chunk off of the output of `git symbolic-ref` using some shell string hackery.

  function lambda_or_delta {
      dirty=$([[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*")
      if [[ $dirty == "*" ]]; then
      echo "Δ"
      return
      fi
      echo "λ"
  }

Being an Emacs fanatic I have a flair for the functional, so I prefer a lambda symbol to the usual `$` prompt. But I also like to know at a glance if I have untracked changes. The dirty variable is checking if there is anything to `git diff` if no then the command fails so the `echo ""` never runs. If there are uncommitted changes then the `git diff` succeeds so the `echo ""` is run and this function then instead prints a delta symbol instead of the lambda.

  function last_command_status {
      if [[ $? == "0" ]]; then
      return
      fi

      echo "$?!! "
  }

If the last command failed this prints the return code and some exclamation points at the front of the prompt.

  function remote_hostname {
      if [ -n "$SSH_CLIENT"  ] || [ -n "$SSH_TTY"  ]; then
      echo "@$HOSTNAME "
      fi
  }

One time I put my bashrc on a remote machine and when I SSH’d into it I realized I couldn’t tell the difference between my local terminal and the remote terminal. This adds the hostname to the front of the prompt if we’re connected via SSH.

  function PWD {
      dir=`pwd | awk -F\/ '' | sed 's/ /\\//'`
      echo "$dir"
  }

Originally my prompt only showed the current directory (so instead of `/home/chasinglogic` it would show `chasinglogic`). But I work on a lot of Python projects and the project folders are always the name of the package and so is the package folder inside. That made it really annoying to tell where I was so a little Stack Overflow and awk magic later I now show instead the current directory and it’s direct parent.

  PS1="\[$RED\]\$(last_command_status)\[$VIOLET\]\$(remote_hostname)\[$BLUE\]\$(PWD) \[$CYAN\]\$(parse_git_branch)\[$ORANGE\]\$(lambda_or_delta) \[$NO_COLOR\]"

This is where it all comes together. Bash uses the `$PS1` variable to create your prompt so all we’re doing here is calling each of our prompt functions and colorizing the output. The weird escaping of the `\[` and `\]` is because ANSI color codes are weird. Just go with it.

Functions and Aliases

These are all of my aliases and utility functions to make working in Bash a pleasant experience.

Tmux

As previously mentioned I used to use Tmux so these are all things that make Tmux nice to use:

  function t {
      tmux new-session -A -s $(pwd | awk -F\/ '')
  }

Start a session with the name of the current directory or attach to one if it exists. This is really nice when working on multiple projects you `cd` into the project directory run `t` and now you can easily find that session again later.

  function ts {
      if [[ $TMUX != "" ]]; then
      tmux switch-client -t $(tmux list-sessions -F "#S" | fzf)
      return
      fi

      tmux attach -t $(tmux list-sessions -F "#S" | fzf)
  }

This uses a fun combo of `fzf` and `tmux` subcommands to allow a searchable list of open tmux sessions then you can select from the menu and attach to it. The if statement makes it so you can run this regardless or whether or not you are in a tmux session.

  function irc {
      if [[ $TMUX != "" ]]; then
      tmux neww -ad -n "irc" "weechat"
      return
      fi

      weechat
  }

Before Emacs and Matrix took over my life I used weechat for IRC, this was a nice function to open a dedicated tmux “window” for running weechat.

  alias tm="tmux"
  alias main="tmux new-session -A -s main"

This let me have an always accessible tmux session named main without having to remember those crazy flags.

  alias vim="emacsclient -t -a \"\""

Old habits die hard.

  function EMACS_IS_RUNNING() {
      ps -ef | grep -i emacs | grep -v grep
  }

This is a utility function that returns output if emacs is running and no output if not.

  function e() {
      if [[ $(EMACS_IS_RUNNING) == "" ]]; then
      echo "Emacs isn't running, starting Emacs..."
      emacs --daemon
      fi

      emacsclient -t $@
  }

This basically aliases emacs to e but instead will start an emacs server if one isn’t running. This is nice because emacs can be a little slow to start but if I’ve already started it this just uses that existing session.

  function em() {
      if [[ $(EMACS_IS_RUNNING) == "" ]]; then
      echo "Emacs isn't running, starting Emacs..."
      emacs --daemon
      fi

      emacsclient $@ &
  }

Graphical version of the function above.

  alias ll="ls -alF"
  alias la="ls -a"
  alias l="ls -CF"
  alias sl="ls"

You can never have too many of these

  if [[ -x /bin/dircolors ]]; then
      alias ls="ls --color"
      alias ll="ls --color -alF"
      alias la="ls --color -a"
      alias l="ls --color -CF"
  fi

Adds colored versions of the above if using GNU ls.

  alias cd..="cd .."
  alias cdc="cd $HOME/Code"
  alias cdn="cd $HOME/Notes"

Let me cd to some common directories quickly.

  alias apt="sudo apt"
  alias zyp="sudo zypper"
  alias dnf="sudo dnf"
  alias pck="packer"
  alias pac="sudo pacman"

Short hand commands for package managers, avoids typing sudo all the time.

  alias p="python"
  alias p3="python3"
  alias n="npm"
  alias nd="node"
  alias ve="python3 -m venv"
  alias venv="python3 -m venv"
  alias v="source venv/bin/activate"
  alias vp="source venv_pypy/bin/activate"

Let me run super common commands in a few keystrokes.

  alias systemctl="sudo systemctl"
  alias sctl="sudo systemctl"
  alias pg="sudo systemctl start postgresql"

I always forget to sudo systemctl for some reason.

  alias g="git"
  alias gc="git commit -v"
  alias ga="git add"
  alias gb="git branch"
  alias gp="git push"
  alias gpl="git pull"
  alias gck="git checkout"
  alias gcp="git commit -v && git push"
  alias gst="git status"

I think I stole these from oh-my-zsh but oh-well.

  # Add fzf functionality
  [ -f ~/.fzf.bash ] && source ~/.fzf.bash

This enables fzf.

The End

We made it. Whew, there’s more there than I actually remembered but I used all of these functions and aliases daily at one point and time or still do. I hope that you found these useful!

Share some of your favorite bash hacks with me on Twitter [@chasinglogic](https://twitter.com/chasinglogic)

Linux, Emacs, and FOSS Enthusiast. I do the Dev and the Ops and sometimes both at once.

Home