# Pimp Up Your Shell
A guide for boosting your macOS shell experience.
# How Do You Call Your Workstation?
Give your box a name please: (⚠️ Change rafi-mac
to something of yours)
hostname
export COMPUTER_NAME="rafi-mac"
sudo scutil --set ComputerName "${COMPUTER_NAME}"
sudo scutil --set HostName "${COMPUTER_NAME}"
sudo scutil --set LocalHostName "${COMPUTER_NAME}"
sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server NetBIOSName -string "${COMPUTER_NAME}"
You might need to reboot your laptop after this. :face_with_rolling_eyes:
Check-out https://mths.be/macos for awesome ways to configure your macOS.
# Most Frequently Used Shells
- Bash - (Bourne Again Shell) Default on many Linux distros.
- Tcsh - Enhanced C shell.
- Ksh - Korn shell.
- Zsh - Incorporates many features of other Unix/GNU Linux shells.
- Fish - Friendly interactive shell.
# Apple Packs Outdated Software
$ echo $BASH_VERSION
3.2.57(1)-release
$ git version
git version 2.17.1
On July 27, 2004, Chet Ramey released version 3 of Bash.
# Let's Install Newest Bash!
brew uninstall --force bash-completion
brew install readline bash bash-completion@2
# Set New Bash As Default Shell
echo "/usr/local/bin/bash" | sudo tee -a /etc/shells
chsh -s /usr/local/bin/bash
Now, close all Terminal windows and open them again.
echo $BASH_VERSION
You should see the fresh new Bash version you've installed.
# GNU vs. BSD
POSIX is a set of standards to implement.
GNU tools are basically open versions of tools that already existed but were redone to conform to principals of open software.
Unix and BSD are "older" implementations of POSIX that are various levels of "closed source".
# GNU vs. BSD
Let's install some GNU tools!
brew install coreutils gnutls gawk gnu-sed gnu-tar gnu-which
brew ls coreutils
/usr/local/bin/gsort --help
/usr/bin/sort --help
Note the difference.
# Init Scripts
Remember autoexec.bat
and config.sys
?
@ECHO OFF
PROMPT $P$G
PATH C:\DOS;C:\WINDOWS
SET TEMP=C:\TEMP
SET BLASTER=A220 I7 D1 T2
LH SMARTDRV.EXE
LH DOSKEY
LH MOUSE.COM /Y
# Bash Invocation
Bash behaviour can be altered depending on how it is invoked. If Bash is
spawned by login in a TTY, an SSH daemon, or similar, it is considered
a login shell. This mode can also be engaged using the -l
/--login
option. Bash is considered an interactive shell when its standard input
and error are connected to a terminal, and it is not started with -c
option.
All interactive shells source ~/.bashrc
, while interactive login shells
also source ~/.bash_profile
. Your terminal emulator might be using
a login shell via -l
.
# Fresh New Shell Init Scripts
Let's backup our .bashrc and .bash_profile and start a new fresh beginning.
mv ~/.bashrc ~/.bashrc.old
mv ~/.bash_profile ~/.bash_profile.old
mkdir -p ~/.config/bash
touch ~/.config/bash/bashrc ~/.config/bash/profile
cd ~
ln -s .config/bash/bashrc .bashrc
ln -s .config/bash/profile .bash_profile
# Let's Architect This
cd ~/.config/bash
touch aliases completion exports inputrc utils
mkdir -p ~/.local/{share,bin} ~/.cache
In your new ~/.config/bash/exports
append:
# XDG directories
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_DATA_HOME="$HOME/.local/share"
# Let's Source 'em!
In your new ~/.bash_profile
:
source "$HOME/.config/bash/exports"
source "$XDG_CONFIG_HOME/bash/bashrc"
And in your new ~/.bashrc
:
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
source "$XDG_CONFIG_HOME/bash/completion"
source "$XDG_CONFIG_HOME/bash/aliases"
source "$XDG_CONFIG_HOME/bash/utils"
# Readline
GNU Readline is a software library that provides line-editing and history capabilities for interactive programs with a command-line interface, such as Bash.
# Readline Config
Update ~/.config/bash/inpurc
with following:
# Ring the bell, let other programs handle it (urxvt, tmux, etc.)
set bell-style audible
# Ignore case when matching and completing paths
set completion-ignore-case On
# Treat underscores and hyphens the same way for completion purposes
set completion-map-case On
# Show me up to 5,000 completion items, don't be shy
set completion-query-items 5000
# Don't display control characters like ^C if I input them
set echo-control-characters Off
# Expand tilde to full path on completion
set expand-tilde On
# Preserve caret position while browsing through history lines
set history-preserve-point On
# When completing directories, add a trailing slash
set mark-directories On
# Do the same for symlinked directories
set mark-symlinked-directories On
# on menu-complete, first display the common prefix, then cycle through the
# options when hitting TAB
set menu-complete-display-prefix On
# Don't paginate possible completions
set page-completions Off
# Show multiple completions on first tab press
set show-all-if-ambiguous On
# Don't re-complete already completed text in the middle of a word
set skip-completed-text On
# Show extra file information when completing, like `ls -F` does
set visible-stats on
# Tell Shell Where to Find Things
Append to ~/.config/bash/exports
# Local bin
export PATH="$HOME/.local/bin:$PATH:bin"
# Python per user site-packages directory
export PATH="$PATH:$HOME/Library/Python/3.7/bin"
export PATH="$PATH:$HOME/Library/Python/2.7/bin"
export INPUTRC="$XDG_CONFIG_HOME/bash/inputrc"
export HOMEBREW_GITHUB_API_TOKEN="yourtokenhere"
# Misc defaults
export LESS="-FiQMXR"
export LESSCHARSET="UTF-8"
Generate new token for Homebrew at https://github.com/settings/tokens
# Improve ls
Append to ~/.config/bash/aliases
# Use GNU ls on macOS instead of BSD
hash gls 2>/dev/null && LS="gls" || LS="ls"
# Listing directory contents
alias ls='LC_COLLATE=C '$LS' --color=auto --group-directories-first'
alias l='ls -CFa'
alias ll='ls -alF'
alias lsd='ls -Gal | grep ^d'
unset LS
# Use GNU Tools
Remember we installed 'coreutils
'?
Let's append to ~/.config/bash/aliases
hash gdircolors 2>/dev/null && alias dircolors=gdircolors
hash gsort 2>/dev/null && alias sort=gsort
# Have Fun With Aliases
Append to ~/.config/bash/aliases
# Carry over aliases to the root account when using sudo
alias sudo='sudo '
alias grep="grep --color=auto --exclude-dir=.git"
# File find, if fd is not installed
if ! hash fd 2>/dev/null; then
alias f='find . -iname '
alias ff='find . -type f -iname '
alias fd='find . -type d -iname '
fi
# Head and tail will show as much possible without scrolling
hash ghead 2>/dev/null && alias h='ghead -n $((${LINES:-12}-4))'
hash gtail 2>/dev/null && alias t='gtail -n $((${LINES:-12}-4)) -s.1'
# Shortcuts
alias c='clear'
alias diff='colordiff'
# Storage
alias dut="du -hsx * | sort -rh | head -10"
alias dfu="df -hT -x devtmpfs -x tmpfs"
# Processes
alias process='ps -ax'
alias psk='ps -ax | fzf | cut -d " " -f1 | xargs -o kill'
alias pst='pstree -g 3 -ws'
# Misc
alias fontcache='fc-cache -f -v'
alias freq='cut -f1 -d" " "$HISTFILE" | sort | uniq -c | sort -nr | head -n 30'
alias sniff="sudo ngrep -d 'en1' -t '^(GET|POST) ' 'tcp and port 80'"
alias multiopen='while read i; do open "$i"; done <<<'
alias ungzip='gzip -d'
alias untar='tar xvf'
alias ipinfo="curl -s ipinfo.io"
alias weather="curl -s wttr.in/Tel-Aviv"
# A quick way to get out of current directory
alias ..='cd ..'
alias ...='cd ../../'
alias ....='cd ../../../'
alias .....='cd ../../../../'
# Kubernetes
alias k=kubectl
alias kctx=kubectx
# Git
alias gb='git branch'
alias gc='git checkout'
alias gcb='git checkout -b'
alias gd='git diff'
alias gds='git diff --cached'
alias gf='git fetch --prune'
alias gfa='git fetch --all --tags --prune'
alias gs='git status -sb'
# Docker
alias dps='docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{ .Ports }}\t{{.RunningFor}}\t{{.Command}}\t{{ .ID }}" | cut -c-$(tput cols)'
alias dls='docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{ .Ports }}\t{{.RunningFor}}\t{{.Command}}\t{{ .ID }}" | cut -c-$(tput cols)'
alias dim='docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}\t{{.CreatedSince}}"'
alias dgc='docker rmi $(docker images -qf "dangling=true")'
alias dvc='docker volume ls -qf dangling=true | xargs docker volume rm'
alias dtop='docker stats $(docker ps --format="{{.Names}}")'
alias dnet='docker network ls && echo && docker inspect --format "{{\$e := . }}{{with .NetworkSettings}} {{\$e.Name}}
{{range \$index, \$net := .Networks}} - {{\$index}} {{.IPAddress}}
{{end}}{{end}}" $(docker ps -q)'
alias dtag='docker inspect --format "{{.Name}}
{{range \$index, \$label := .Config.Labels}} - {{\$index}}={{\$label}}
{{end}}" $(docker ps -q)'
# Improve Bash Behavior
Prepend in your ~/.bashrc
# Bash settings
shopt -s cdspell # Auto-corrects cd misspellings
shopt -s checkwinsize # Update the value of LINES and COLUMNS after each command if altered
shopt -s cmdhist # Save multi-line commands in history as single line
shopt -s dotglob # Include dotfiles in pathname expansion
shopt -s expand_aliases # Expand aliases
shopt -s extglob # Enable extended pattern-matching features
shopt -s histreedit # Add failed commands to the bash history
shopt -s histappend # Append each session's history to $HISTFILE
shopt -s histverify # Edit a recalled history line before executing
export HISTSIZE=3000
export HISTFILESIZE=1500000
export HISTTIMEFORMAT='[%F %T] '
export HISTIGNORE='pwd:jobs:ll:ls:l:fg:history:clear:exit'
export HISTCONTROL=ignoreboth
export HISTFILE="$XDG_CACHE_HOME/bash_history"
export VISUAL=vim
export EDITOR="$VISUAL"
export PAGER=less
# Completions!!!!!!
Append in your ~/.config/bash/completion
# Load all completions Homebrew's bash-completion@2 has prepared
if [[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]]; then
. "/usr/local/etc/profile.d/bash_completion.sh"
fi
# Kubernetes
complete -o default -F __start_kubectl k
# Extra macOS stuff
if [[ "$OSTYPE" == "darwin"* ]]; then
# Add tab completion for `defaults read|write NSGlobalDomain`
# You could just use `-g` instead, but I like being explicit
complete -W "NSGlobalDomain" defaults
# Add `killall` tab completion for common apps
complete -o "nospace" -W "Contacts Calendar Dock Finder Mail Safari iTunes SystemUIServer Terminal Twitter" killall
fi
# Docker Completions
Symlink Docker app Bash scripts:
ln -s /Applications/Docker.app/Contents/Resources/etc/docker.bash-completion /usr/local/etc/bash_completion.d/docker
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-machine.bash-completion /usr/local/etc/bash_completion.d/docker-machine
ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.bash-completion /usr/local/etc/bash_completion.d/docker-compose
# More Bash Trickery
Check-out my Bash config (opens new window)
in my github.com/rafi/.config (opens new window) repository.
# Know Your OS Package-Manager
Hello, my name is Homebrew
brew --version
Please upgrade me from time to time...
brew update
brew outdated
brew upgrade
# Install Everyone's Favorite Tools
Don't blindly install all these tools, pick & choose!
brew install bc tree colordiff pstree jq urlview tcpdump nmap \
curl unrar netcat the_silver_searcher figlet p7zip ncdu calc \
watch pidof grc icdiff git-cal git-extras ranger wget rsync \
yamllint spark tig htop fzf fzy
brew install go node yarn python python@2 pipenv
brew install git --with-curl --with-openssl
brew install vim --with-lua --with-override-system-vi
brew install neovim
brew install kubernetes-cli kubectx stern
brew install tmux tmux-mem-cpu-load tmux-xpanes reattach-to-user-namespace
brew install aria2 lnav pngcrush poppler peco diff-so-fancy
brew install fd bat shellcheck ctop httpie
brew install aspell --with-lang-he --without-lang-de --without-lang-es --without-lang-fr
brew install rafi/tap/gits rafi/tap/reg rafi/tap/yaml2json
# Use Homebrew to Install macOS Apps
Some of these are PAID apps!
brew cask install transmission mpv bartender beyond-compare \
clipy contexts dash docker iterm2 karabiner-elements \
keycastr licecap marked slack spotify telegram typora whatsapp
# Install Node Tools
npm -g install reveal-md mad tern write-good
npm -g install jsonlint markdownlint-cli
npm -g install resume-cli imagemin-cli
npm -g ls --depth=0
# Let's Fix Something
What's the most annoying thing when using many terminal windows/tabs?
# Shell History
Remember 'shopt -s histappend
' from our ~/.bashrc
?
This causes shell to append each session's history to $HISTFILE, and not overwrite it.
Great!
But I want it now.™
# Use the PROMPT_COMMAND!
Preserve bash history in multiple terminal windows:
Insert lines in ~/.bashrc
BEFORE $XDG_CONFIG_HOME/bash/utils
sourcing:
# Append to history and re-read new history lines immediately
PROMPT_COMMAND="history -a; history -n; ${PROMPT_COMMAND}"
Want only to immediately append?
# Append to history immediately, but don't re-read history
PROMPT_COMMAND="history -a; ${PROMPT_COMMAND}"
Now open a few terminals, and test it!
# Let's Add Some Colors
Remember we've installed 'coreutils
'?
We've also aliased 'dircolors' to 'gdircolors'.
The program 'ls
' uses the environment variable LS_COLORS
to determine the
colors in which the filenames are to be displayed.
# Find a Cool LS_COLORS Theme
Find a cool theme (opens new window)
in the internet and move it to ~/.config/bash/dircolors
curl -LO https://gist.github.com/clsn/1728412/raw/3f27dd4ece98f6ffa5ceba5bdcce536beba06b75/.dir_colors
mv .dir_colors ~/.config/bash/dircolors
# Apply LS_COLORS
Append to your ~/.bashrc
# Load directory and file colors for GNU ls
eval "$(dircolors -b "$XDG_CONFIG_HOME/bash/dircolors")"
Open a new terminal and run:
ls -alp
You should see colorful filenames and directories.
# Pimp up ls
Output
brew install grc
grc ls -alph
Use with netstat, ping, tail, ps, and more.
# The Shell is The Best File-manager
Let's improve our speed of 'cd
' by a bazillion!
# Introducing… z
After a short learning phase, z will take you to the most 'frecent' directory that matches ALL of the regexes given on the command line, in order.
brew info z
brew install z
Append to ~/.config/bash/utils
# https://github.com/rupa/z
# Must be loaded _after_ setting PROMPT_COMMAND
if [ -f "/usr/local/etc/profile.d/z.sh" ]; then
. "/usr/local/etc/profile.d/z.sh"
fi
# Train z
z
has to be trained. cd
into your favorite directories.
cd ~/code/work/ansible
cd ~/code/dev/myproject
cd ~/.config
cd ~/.vim
Check z
's listing:
$ z
112.274 /Users/rafi/code/tikal/dartagnan-infra
148.375 /Users/rafi/code/dev/acme/docker-k8s-101
275.512 /Users/rafi/code/dev/acme
311.72 /Users/rafi/code/acme/pimpupyourshell
663.29 /Users/rafi/code/work/foo/autobots
21717.8 /Users/rafi/code/work/foo/autobots-k8s-onprem
# Use z
$ z foo auto
$ pwd
/Users/rafi/code/work/foo/autobots
$ z prem
$ pwd
/Users/rafi/code/work/foo/autobots-k8s-onprem
$ z dart.*infra
$ pwd
/Users/rafi/code/tikal/dartagnan-infra
# grep
is so Darn Slow
$ cd ~/code
$ time grep -ri bob * >/dev/null
real 1m48.005s
user 1m26.262s
sys 0m10.616s
# Introducing… ag
A code-searching tool similar to ack, but faster.
$ cd ~/code
$ time ag bob >/dev/null
real 0m1.120s
user 0m1.266s
sys 0m1.322s
Install ag
(opens new window):
brew install the_silver_searcher
# What's so great about Ag?
- It is an order of magnitude faster than ack.
- It ignores file patterns from your .gitignore and .hgignore.
- If there are files in your source repo you don't want to search, just add their patterns to a .ignore file. (*cough* *.min.js *cough*)
- The command name is 33% shorter than ack, and all keys are on the home row!
# SSH-Keys
You don't use passphrases for your keys?
With SSH keys, if someone gains access to your computer, they also gain access to every system that uses that key. To add an extra layer of security, you can add a passphrase to your SSH key. You can use ssh-agent to securely save your passphrase so you don't have to reenter it.
# Use ssh-agent
to Remember Your Passphrases
I don't use macOS keychain, I use a very popular one called keychain
(opens new window).
brew info keychain
brew install keychain
Append to your ~/.bash_profile
if hash keychain 2>/dev/null; then
eval `keychain --eval --agents ssh --inherit any --quiet id_rsa`
fi
Read about '--inherit any
' here (opens new window).
# keychain
Using the alternative keychain
(opens new window), every
time you restart your computer, and open a new terminal, you will be asked
once for the passphrase of id_rsa
.
# git
is Glorious.
# git log
is Ugly
Teach git some new pretty formats, append to ~/.gitconfig
[pretty]
log = %C(240)%h%C(reset) -%C(auto)%d%Creset %s %C(242)(%an %ar)
detailed = %C(cyan)%h %C(red)%ad %C(blue)[%an]%C(magenta)%d %C(white)%s
shorter = %C(auto)%D %C(240)--%C(242)%gD%N %ad by %C(white)%cn%C(reset)
# Git Has It's Own Aliases
Teach git some new tricks: (append to ~/.gitconfig
)
[alias]
log = log --pretty=log
lb = log --graph --simplify-by-decoration --pretty=shorter --all --notes --date-order --relative-date
lg = log --graph --pretty=log --all
lgd = log --graph --pretty=log
lgw = !sh -c '"while true; do clear; git lg -15; sleep 5; done"'
Let's add some Bash aliases: (append to ~/.config/bash/aliases
)
alias gfl='git fetch --prune && git lg -15'
alias gl='git lg -15'
alias gll='git lg'
alias gld='git lgd -15'
# Try it Out
gl
gll
gld
git lb
git lgw
Great visibility, great benefits. Understand repository history better.
# Mooooore Git Aliases
[alias]
s = status -sb
f = fetch --prune
c = commit -v
cm = commit -vm
br = branch -v
st = status
ck = checkout
t = tag --column
tn = tag -n
d = diff
ds = diff --staged
dw = diff --color-words
dh = diff --color-words HEAD
dp = !git log --pretty=oneline | fzf | cut -d ' ' -f1 | xargs -o git show
lcrev = log --reverse --no-merges --stat @{1}..
lcp = diff @{1}..
patch = !git --no-pager diff --no-color
prune = fetch --prune
stash-all = stash save --include-untracked
sm = submodule
smu = submodule foreach git pull origin master
snapshot = !git stash save "snapshot: $(date)" && git stash apply "stash@{0}"
snapshots = !git stash list --grep snapshot
w = whatchanged --decorate
wp = whatchanged --decorate -p
# Better Git Diffs
Use diff-so-fancy (opens new window)
# Diff So Fancy
brew info diff-so-fancy
brew install diff-so-fancy
Edit your ~/.gitconfig
[pager]
show-branch = true
status = true
diff = diff-so-fancy | less --tabs=1,3
show = diff-so-fancy | less --tabs=1,3
# More Git Trickery
Check-out my git/config (opens new window)
in my github.com/rafi/.config (opens new window) repository.
# Other Great Tools
- lf (opens new window)
- entr (opens new window)
- ttyd (opens new window)
- pass (opens new window) and pineentry-mac (opens new window)
- fd (opens new window)
- editorconfig (opens new window)
- rclone (opens new window)
- task (opens new window)
- alacritty (opens new window)
- neovim (opens new window)