Thu, May 11, 2017
#programming #bash #sysadmin
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
