Terminal and Bash - Tips & Tricks

A collection of notes, tips and tricks about how I setup and use my terminal (iTerm2) and shell (Bash). Perhaps you find some of it useful or interesting for your own setup.

See also this post about my Mac dev setup & favorite tools.

I’d love to hear your thoughts and additional tips in the comments below!


Contents


iTerm2 setup

On MacOS, I’m a happy user of iTerm2, with Bash as default shell. Possible alternatives to iTerm2 might include Hyper and Warp.

Customize your iTerm profile
  • In the settings, duplicate the standard profile, and make the new one default.
  • Set a larger initial window size, i.e. to 240x60 characters.
  • Open new tabs in previous location.
  • Adjust fonts & color scheme to your liking.
  • You can assign color and custom title to tabs (via right click on the tab title).

Terminal example

You can download my personal profile here and keymap here.

  • I’m using alt + t to open iTerm or switch to it if already opened (using an Alfred App workflow)
  • Open a new tab: + t
  • Navigate tabs with + ⬅/➡ as well as with + 1/2/3/4/…
  • Navigate words using alt + ⬅/➡ (read more here)
  • Split a window into multiple panes:
    • Split vertically: + d
    • Split horizontally: + shift + d
    • Navigate panes with + alt + ⬅/➡/⬆/⬇
    • Close the current pane: ctrl + d
  • Close a full tab with + w

Bash

I recommend to use Bash as default shell, to stay close to Bash because it’s used everywhere (servers, linux, containers, etc.) and for being compatible with standard Bash scripts.

On MacOS, you can set your user’s default shell to Bash with chsh -s /bin/bash (requires a logout and login).

Dotfiles

“Dotfiles” are the configuration files of your shell and other tools. These config files are located in your home directory and start with a ., i.e. ~/.bash_profile, ~/.vimrc, etc.

There are several posts about dotfile setups with various complexity.

I prefer this relatively straight-forward setup:

Some of my favorite Bash tips & tricks
  • Unlimited shell history! Never forget commands used in the past - more details here.
    # unlimited bash history: https://stackoverflow.com/a/19533853
    export HISTSIZE=
    export HISTFILESIZE=
    
  • Fuzzy search Bash history using fzf (hotkey: ctrl + r):

    (You can also use fzf to navigate git 💡)
  • zoxide to quickly navigate paths based on history, using the commands z and zi

Homebrew & basic tools

I like using Homebrew as package manager and for installing additional programs.

  • Install a modern version of Bash:
    brew install bash bash-completion2
    
  • Install often used packages:
    brew install git openssl vim gh imagemagick libyaml coreutils gpg-suite-no-mail tee
    
  • I prefer the original GNU version of common tools like tar, sed, awk, grep, find, etc. (to be compatible with what’s available on servers and expected in Bash scripts). Read more about the setup here:
    # Install gnu utils: https://apple.stackexchange.com/a/69332
    brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt grep
    
  • Find more good Homebrew recommendations here: https://github.com/mathiasbynens/dotfiles/blob/main/brew.sh

Newer tools


Git

  • My .aliases file has a bunch of very useful git aliases:
    # Git
    alias g="git"
    alias ggo="git checkout"
    alias gs='git status -sb'
    alias ga='git add '
    alias gb='git branch '
    alias gc='git commit'
    alias gd='git diff'
    alias gds='git diff --staged'
    alias gl='git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short'
    alias gca="git commit -a --amend --reuse-message=HEAD"
    
    # Switch to common branch, pull and prune
    alias g-main="git checkout main && git pull && git fetch -p"
    alias g-dev="git checkout develop && git pull && git fetch -p"
    
    # git+github: check out PR by number
    alias ghpr='gh pr checkout'
    
    # git: create a PR
    alias pr-create='gh pr create -f'
    alias pr-create-draft='gh pr create -f -d'
    
  • Enable git bash completion for g like this.
  • There’s also several additional useful aliases in the ~/.gitconfig file.
  • Special shoutout to git rb to interactively switch between most recently used branches (h/t @drog_v):
    rb = !git reflog | egrep -io \"moving from ([^[:space:]]+)\" | awk '{ print $3 }' | awk ' !x[$0]++' | egrep -v '^[a-f0-9]{40}$' | fzf | xargs git checkout
    
  • I recommend to enable pretty diffs with https://github.com/so-fancy/diff-so-fancy
  • Additionally, there’s also fzf for common git tasks: https://github.com/junegunn/fzf-git.sh

Bash functions + aliases

For ease of reference, here is the content of my ~/.aliases and ~/.functions files:

~/.aliases

# Easier navigation: .., ..., ...., ....., ~ and -
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."
alias .....="cd ../../../.."
alias ~="cd ~" # `cd` is probably faster to type though
alias -- -="cd -"

# Open current path in VS Code
alias c="code-insiders ."

# v shows current git code hash + tag + number of commits since
# For example: v1.6.1-3-g3265e6d (3 commits after tag v1.6.1 with current hash 3265e6d)
alias v="git describe --tags --always --dirty="-dev""

# List all files colorized in long format
alias l="ls -lhF ${colorflag}"

# List all files colorized in long format, excluding . and ..
alias ll="ls -lAhF ${colorflag}"

# List only directories
alias lsd="ls -lF ${colorflag} | grep --color=never '^d'"

# Always use color output for `ls`
alias ls="command ls ${colorflag}"

# lt runs "make lint" and "make test"
alias lt="make lint && make test"

# Git
alias g="git"
alias ggo="git checkout"
alias gs='git status -sb'
alias ga='git add '
alias gb='git branch '
alias gc='git commit'
alias gd='git diff'
alias gds='git diff --staged'
alias gl='git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short'

alias gca="git commit -a --amend --reuse-message=HEAD"
alias gcn="git commit --no-verify"
alias gdp='git diff | less'
alias gb-current='git branch --show-current'

# Switch to common branch, pull and prune
alias g-main="git checkout main && git pull && git fetch -p"
alias g-dev="git checkout develop && git pull && git fetch -p"

# git+github: check out PR by number
alias ghpr='gh pr checkout'

# git: create a PR
alias pr-create='gh pr create -f'
alias pr-create-draft='gh pr create -f -d'

# Kubernetes
alias k="kubectl"

alias kg="k get"
alias kd="k describe"

alias kgp="kg pods"
alias kgpw="kg pods --watch"
alias kgd="kg deploy"
alias kgn="kg nodes"
alias kdp="kd pod"
alias kdd="kd deploy"

alias ktop="k top pod"
alias kexec="k exec -ti "
alias klog="k logs"
alias klogf="k logs -f"
alias klogf5m="k logs --since 5m -f"
alias klogf1m="k logs --since 1m -f"
alias klogf1s="k logs --since 1s -f"
alias klogprev="k logs --tail 40 --previous"

# Generate a uuid-v4
alias uuid='uuidgen | tr "[:upper:]" "[:lower:]"'

# SSH with password
alias sshpwd="ssh -o PreferredAuthentications=password"

# grep and find shortcut
alias gr="grep -nir"
alias f="find ./ -name"

# Show / copy SSH pubkey
alias pubkey="cat ~/.ssh/id_rsa.pub"
alias pubkeycopy="cat ~/.ssh/id_rsa.pub | pbcopy"

# Get week number
alias week='date +%V'

# Stopwatch
alias timer='echo "Timer started. Stop with Ctrl-D." && date && time cat && date'

# Show IP
alias ip="dig +short myip.opendns.com @resolver1.opendns.com"

# Print each PATH entry on a separate line
alias path='echo -e ${PATH//:/\\n}'

# Reattach to a detached screen session
alias sr="screen -d -r"

# Empty the Trash on all mounted volumes and the main HDD. Also, clear Apple’s System Logs to improve shell startup speed.
# Finally, clear download history from quarantine. https://mths.be/bum
alias emptytrash="sudo rm -rfv /Volumes/*/.Trashes; sudo rm -rfv ~/.Trash; sudo rm -rfv /private/var/log/asl/*.asl; sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV* 'delete from LSQuarantineEvent'"

# Show/hide hidden files in Finder
alias show="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder"
alias hide="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder"

# Hide/show all desktop icons (useful when presenting)
alias hidedesktop="defaults write com.apple.finder CreateDesktop -bool false && killall Finder"
alias showdesktop="defaults write com.apple.finder CreateDesktop -bool true && killall Finder"

# URL-encode strings
alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1]);"'

# Merge PDF files, preserving hyperlinks
# Usage: `mergepdf input{1,2,3}.pdf`
alias mergepdf='gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=_merged.pdf'

~/.functions

#!/usr/bin/env bash

# Create a new directory and enter it
function mcd() {
	mkdir -p "$@" && cd "$_";
}

# Change working directory to the top-most Finder window location
function cdf() { # short for `cdfinder`
	cd "$(osascript -e 'tell app "Finder" to POSIX path of (insertion location as alias)')";
}

# Push the current branch to git "origin" remote
function gitpb() {
    branch=$( git rev-parse --abbrev-ref HEAD )
    cmd="git push origin $branch"
    echo $cmd
    $cmd
}

# git push origin <current-branch> --no-verify --force
function gitpbf() {
    branch=$( git rev-parse --abbrev-ref HEAD )
    cmd="git push origin $branch --no-verify --force"
    echo $cmd
    $cmd
}

# Delete all git branches except current
function git-delete-branches {
    branch=$( git rev-parse --abbrev-ref HEAD )
    cmd="git branch | grep -v \"$branch\" | xargs git branch -D"
    read -p "This will delete all branches except $branch. Are you sure? " -n 1 -r
    echo $cmd
    bash -c "$cmd"
}

# Set the current branch to track origin with the same branch name
function git-set-upstream {
    branch=$( git rev-parse --abbrev-ref HEAD )
    cmd="git branch -u origin/$branch"
    echo $cmd
    $cmd
}

# Copy the file contents to clipboard
function catcopy() {
    cat $1 | pbcopy
}

# curl, but show only the headers
function curl-headers() {
    curl -s -D - "$@" -o /dev/null
}

# curl and JSON prettify the response body
function cjq () {
    curl -s "$@" | jq;
}

# Run bash in a docker container
function docker-bash() {
    docker exec -it $1 /bin/bash
}

# Run docker container, mount current directory to /mnt/, and remove after stop
function docker-run() {
    CMD="docker run --rm -it -w /mnt -v $(pwd):/mnt $@"
    echo "$CMD"
    $CMD
}

Looking up functions and aliases

If you want to find out what’s behind a command, alias or function, you can use type:

$ type g
g is aliased to `git'

$ type mcd
mcd is a function
mcd ()
{
    mkdir -p "$@" && cd "$_"
}

I hope some of these are useful or interesting to you, and perhaps provide some inspiration for your personal setup. I’d love to hear your thoughts and additional tips in the comments below!