A guided Tour of My Bashrc

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. 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.

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 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

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

Home