Play around with your command shell, and you’ll be surprised at how much more productive it makes you, The Pragmatic Programmer
Unix shells are interactive interfaces that allow users to interact with the Operating System by executing commands and utilities. There are three main shells: bash, Zsh, and fish.


Some features: If you want to change directory, it is not necessary to enter the cd command (automatic cd), i.e. Documents instead of cd Documents and you don’t have to type whole directory names either, ls Dro/do/Ba(<Tab>) becomes Dropbox/docs/Basic. tree -<Tab> suggest a list of options for tree, with short descriptions, ls **/Linux.md performs a recursive search and returns Dropbox/docs/Linux.md
cat /etc/shells, then make it your default shell by using chsh -s /usr/bin/zsh username. After that, you need to log out, then log back in for the changes to take effect. Don’t worry, you can always revert to Bash by typing: chsh -s $(which bash). sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
zsh-autosuggestions, is a fish-like fast and unobtrusive autosuggestions for zsh, a real time saver! Installation with Oh-my-zsh: Clone its repository into ~/.oh-my-zsh/custom/plugins…
# Linux
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
# ... and add the plugin to the list of plugins for Oh My Zsh to load, nvim ~/.zshrc:
plugins=(zsh-autosuggestions)
# macOS
brew install zsh-autosuggestions
source $HOMEBREW_PREFIX/share/zsh-autosuggestions/zsh-autosuggestions.zsh
From now on, as you type a command, Zsh will make suggestions. If the suggestion is the one you are looking for, just press the → (arrow right) key to accept it.
zsh-syntax-highlighting. It provides syntax highlighting for the shell zsh — commands turn green if valid, red if not.
Installation with a plugin manager (Oh-my-zsh). Clone its repository…
# Linux
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
# and activate the plugin in ~/.zshrc:
plugins=( [plugins...] zsh-syntax-highlighting)
# macOS
brew install zsh-syntax-highlighting
# ... and add the plugin to the list of plugins for Oh My Zsh to load, nvim ~/.zshrc:
source $HOMEBREW_PREFIX/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
# zsh-syntax-highlighting must be sourced last in your .zshrc — after all other plugins and before any command that produces output. I
Search through history. Typically, you may want to use the history command or some variations, e.g., history | grep ssh, history | less, etc.
If you press the ↑ key, you could browse for any particular command you have used recently. Now, if you entered a command, e.g., grep, mkdir or ls, and then press the ↑ key, you could browse for the recent history where this particular order was used.
Navigate super fast using z. It tracks your most used and recently visited directories. It allows you to jump quickly to them by typing z followed by any word (fuzzy search) that is in your desired path, e.g., z Dropbox(<Tab>) becomes /home/nmaximo7/Dropbox, or z content(<Tab>) becomes /home/nmaximo7/JustToThePoint/content/.
Starship is a minimal, blazing-fast, and infinitely customizable prompt for any shell: ``. Then, you may want to configure it:
# Linux
curl -sS https://starship.rs/install.sh | sh
# macOS
brew install starship
# Add to ~/.zshrc:
eval "$(starship init zsh)"
# Configure with a preset.
# A preset is a ready-made configuration—like a starter theme—you can use as-is or customize later.
mkdir -p ~/.config
starship preset nerd-font-symbols -o ~/.config/starship.toml
Nerd Fonts Installation:
# Meslo LGS NF is a recommended font:
brew install --cask font-meslo-lg-nerd-font
# Other popular Nerd Fonts:
brew install --cask font-fira-code-nerd-font
brew install --cask font-hack-nerd-font
brew install --cask font-jetbrains-mono-nerd-font
The configuration file for zsh is called .zshrc and lives in your home folder (~/.zshrc): nvim ~/.zshrc:
# =============================================================================
# ~/.zshrc (stow package: zsh/.zshrc) macOS, Apple Silicon, Ghostty, Starship
# Install: brew install eza bat fd fzf zoxide yt-dlp mpv zsh-autosuggestions zsh-syntax-highlighting
# Move your current ~/.zshrc and ~/.zsh_aliases into macOS/zsh/ first, then run stow -v --no-folding -t ~ zsh.
#
# ORDER MATTERS in this file. Each section depends on the previous ones:
# 1. PATH + Homebrew everything below needs `brew` and your tools on PATH
# 2. Environment variables editor, language, pager
# 3. Oh My Zsh framework: plugins, git aliases, completion system
# 4. Shell behaviour history, options, completion look, key bindings
# 5. Tools fzf (Ctrl-R / Ctrl-T / Alt-C) and zoxide (smart cd)
# 6. Your files aliases, functions, machine-specific overrides
# 7. Prompt Starship
# 8. Plugins that wrap keys autosuggestions, then syntax highlighting LAST
#
# Handy: reload = exec zsh | zshbench = how long startup takes
# zshprof = which line is slow | ezsh = edit this file
# =============================================================================
# Profiling switch: `ZPROF=1 zsh -i -c exit` prints a table of where startup time goes.
[[ -n $ZPROF ]] && zmodload zsh/zprof
# Directory with your dotfiles repo. Aliases and functions refer to it.
export DOTFILES="$HOME/macOS"
# =============================================================================
# 1. PATH + HOMEBREW
# =============================================================================
# `typeset -U` keeps PATH free of duplicates, so re-sourcing this file never grows it.
typeset -U path PATH
# Apple Silicon installs Homebrew in /opt/homebrew (Intel: /usr/local). `brew shellenv`
# sets PATH, MANPATH, FPATH (so Homebrew completions work) and HOMEBREW_PREFIX.
# Normally ~/.zprofile does this already, so this only runs if it did not.
if [[ -z $HOMEBREW_PREFIX ]]; then
for _brew in /opt/homebrew/bin/brew /usr/local/bin/brew; do
if [[ -x $_brew ]]; then eval "$($_brew shellenv)"; break; fi
done
unset _brew
fi
# Your own scripts first. This is a good place to put aliases and functions.
path=("$HOME/bin" "$HOME/.local/bin" $path)
# =============================================================================
# 2. ENVIRONMENT VARIABLES
# =============================================================================
export LANG="en_US.UTF-8" # English messages + UTF-8 everywhere
export CLICOLOR=1 # colour for the BSD `ls` that ships with macOS
# EDITOR/VISUAL: used by git commit, crontab -e, fc, sudoedit...
# Homebrew puts nvim in /opt/homebrew/bin, never /usr/bin, so a hardcoded path breaks.
# $+commands[nvim] is a zsh test that evaluates to 1
# if nvim is known as an available command, and 0 otherwise.
if (( $+commands[nvim] )); then export EDITOR=nvim; else export EDITOR=vim; fi
export VISUAL="$EDITOR"
# GPG (GNU Privacy Guard: signs commits and encrypts files, it is NOT a password
# manager). It needs to know your terminal to ask for the key passphrase.
export GPG_TTY=$(tty 2>/dev/null)
# Coloured man pages through bat (needs: brew install bat). `col -bx` strips the
# backspace-based bold/underline that man emits, bat re-colours the text.
if (( $+commands[bat] )); then
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
fi
# Homebrew: no telemetry, no "hints" after every command.
export HOMEBREW_NO_ANALYTICS=1
export HOMEBREW_NO_ENV_HINTS=1
# fzf: use `fd` (fast, respects .gitignore) when installed, with Catppuccin Mocha colours.
if (( $+commands[fd] )); then
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
fi
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --border \
--color=bg+:#313244,spinner:#f5e0dc,hl:#f38ba8,fg:#cdd6f4,header:#f38ba8 \
--color=info:#cba6f7,pointer:#f5e0dc,marker:#b4befe,fg+:#cdd6f4,prompt:#cba6f7,hl+:#f38ba8"
# =============================================================================
# 3. OH MY ZSH
# =============================================================================
export ZSH="$HOME/.oh-my-zsh"
export ZSH_CUSTOM="$ZSH/custom"
ZSH_THEME="" # Starship draws the prompt, OMZ must not.
zstyle ':omz:update' mode reminder # remind about updates, never update silently
zstyle ':omz:update' frequency 14 # ...at most every 14 days
DISABLE_AUTO_TITLE="true" # Ghostty sets the tab title (shell-integration "title")
# Plugins, only those you really use (each one costs startup time):
# git ~200 aliases: gst (status), gsb (status -sb), gco, gcb, gp, gl (pull), glog...
# brew brew aliases and completions (bubo = update+outdated, bubc = upgrade+cleanup)
# macos ofd (open Finder here), cdf (cd to Finder folder), pfd, quick-look (ql)...
# python/pip py, mkv/vrun (create/activate a venv), pip aliases
# sudo press ESC twice to put `sudo` in front of the current (or last) command
# extract `extract file.zip|tar.gz|7z|...` and the alias `x`: one command for every archive
# web-search `google foo`, `ddg foo`, `github foo` opens the browser
plugins=(git brew macos python pip sudo extract web-search)
# OMZ also runs compinit (the completion system) here, so do NOT call compinit again.
[[ -r $ZSH/oh-my-zsh.sh ]] && source "$ZSH/oh-my-zsh.sh"
# =============================================================================
# 4. SHELL BEHAVIOUR (after OMZ so these settings win)
# =============================================================================
# ---- History ----------------------------------------------------------------
HISTFILE="$HOME/.zsh_history"
HISTSIZE=120000 # kept in memory. Keep it larger than SAVEHIST so duplicates can be
SAVEHIST=100000 # expired first (HIST_EXPIRE_DUPS_FIRST) before real history is lost.
setopt EXTENDED_HISTORY # save a timestamp and duration with every command
setopt SHARE_HISTORY # all open tabs share one history. This ALSO appends
# immediately, so INC_APPEND_HISTORY is not needed (the zsh
# manual says to turn it off when SHARE_HISTORY is on)
setopt HIST_IGNORE_DUPS # skip a command identical to the previous one
setopt HIST_EXPIRE_DUPS_FIRST # when full, drop duplicates before unique entries
setopt HIST_FIND_NO_DUPS # Ctrl-R / search do not show the same command twice
setopt HIST_IGNORE_SPACE # a command starting with a space is not saved (for secrets)
setopt HIST_REDUCE_BLANKS # trim superfluous blanks
setopt HIST_VERIFY # `!!` and `!$` are shown first, you press Enter to run them
# Never save these: trivial commands, and lines that contain secrets.
HISTORY_IGNORE="(ls|ll|la|cd|pwd|exit|clear|history|reload|*TOKEN=*|*PASSWORD=*|*SECRET=*|*API_KEY=*)"
# ---- Options -----------------------------------------------------------------
setopt AUTO_CD # typing a folder name cd's into it
setopt AUTO_PUSHD # every cd is pushed on a stack: `dirs -v`, then `cd -2`
setopt PUSHD_IGNORE_DUPS
setopt PUSHD_SILENT
setopt INTERACTIVE_COMMENTS # allow `# comments` when typing commands
setopt COMPLETE_IN_WORD # complete in the middle of a word
setopt NO_BEEP
# ---- Completion look ---------------------------------------------------------
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # case-insensitive: `cd doc` finds Documents
zstyle ':completion:*' menu select # arrow-key menu for completions
zstyle ':completion:*:descriptions' format '%F{8}── %d ──%f' # dim group headings
# ---- Key bindings ------------------------------------------------------------
# Up/Down search the history for commands that START WITH what you already typed.
zmodload zsh/terminfo
autoload -Uz up-line-or-beginning-search down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search
[[ -n ${terminfo[kcuu1]} ]] && bindkey "${terminfo[kcuu1]}" up-line-or-beginning-search
[[ -n ${terminfo[kcud1]} ]] && bindkey "${terminfo[kcud1]}" down-line-or-beginning-search
# Option+Left/Right jump by word: works because Ghostty has `macos-option-as-alt = true`.
# =============================================================================
# 5. TOOLS
# =============================================================================
# fzf: Ctrl-R fuzzy history, Ctrl-T pick a file, Alt-C jump to a folder.
# (`fzf --zsh` needs fzf >= 0.48; Alt-C needs macos-option-as-alt in Ghostty.)
(( $+commands[fzf] )) && source <(fzf --zsh)
# zoxide: `z foo` jumps to the folder you use most that matches "foo"; `zi` picks interactively.
(( $+commands[zoxide] )) && eval "$(zoxide init zsh)"
# =============================================================================
# 6. YOUR FILES
# =============================================================================
[[ -f ~/.zsh_aliases ]] && source ~/.zsh_aliases # aliases (override OMZ ones)
[[ -f ~/.zsh_functions ]] && source ~/.zsh_functions # functions
[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local # this Mac only / secrets, NOT in git
# =============================================================================
# 7. PROMPT: Starship (config lives in ~/.config/starship.toml)
# =============================================================================
(( $+commands[starship] )) && eval "$(starship init zsh)"
# =============================================================================
# 8. PLUGINS THAT WRAP KEY WIDGETS (must be last)
# =============================================================================
# Install once: brew install zsh-autosuggestions zsh-syntax-highlighting
# ${HOMEBREW_PREFIX} is already known, no slow `brew --prefix` call (~50 ms each).
# autosuggestions: grey ghost text from history, then from completion.
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' # dim grey
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=40 # skip suggestions for very long lines (faster)
if [[ -r $HOMEBREW_PREFIX/share/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then
source "$HOMEBREW_PREFIX/share/zsh-autosuggestions/zsh-autosuggestions.zsh"
bindkey '^ ' autosuggest-accept # Ctrl+Space accepts (Right arrow works too)
fi
# syntax highlighting (red = unknown command, green = valid). Its README says it must be
# sourced AFTER every other plugin and widget, so it goes at the very end.
if [[ -r $HOMEBREW_PREFIX/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]]; then
source "$HOMEBREW_PREFIX/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
fi
[[ -n $ZPROF ]] && zprof
After editing the configuration file for Z shell, you need to either start a new shell instance or type: source ~/.zshrc to apply the changes you have just made. Next time, you could use the alias “szsh”.

Aliases are essentially shortcuts, a means of avoiding typing or remembering a long command sequence that you use often.
# =============================================================================
# ~/.zsh_aliases (stow package: zsh/.zsh_aliases)
# An alias is a short text substitution: it is expanded when you press Enter.
# Anything that needs arguments in the middle, or logic, lives in ~/.zsh_functions.
# Loaded AFTER Oh My Zsh, so an alias here overrides a plugin alias of the same name.
# `(( $+commands[tool] ))` is true when `tool` is installed, so nothing breaks on a new Mac.
# =============================================================================
# ---- Navigation -------------------------------------------------------------
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias dots='cd "$DOTFILES"'
# AUTO_CD is on: typing a folder name alone also changes into it. `d` (OMZ) lists the
# folder stack, `cd -2` jumps back to entry 2.
# ---- Listing: eza when installed (brew install eza), plain ls otherwise ----------
if (( $+commands[eza] )); then
alias ls='eza --icons=auto --group-directories-first'
alias ll='eza -lah --git --header --icons=auto --group-directories-first' # long, hidden, git status
alias la='eza -a --icons=auto --group-directories-first'
alias lt='eza --tree --level=2 --icons=auto --git-ignore' # tree, 2 levels
else
alias ls='ls -G'
alias ll='ls -lAhG'
alias la='ls -A'
fi
# ---- Editor / shell ------------------------------------------------------------
if (( $+commands[nvim] )); then
alias v='nvim'
alias vim='nvim'
alias vi='nvim'
fi
alias ezsh='$EDITOR ~/.zshrc'
alias ealias='$EDITOR ~/.zsh_aliases'
alias efunc='$EDITOR ~/.zsh_functions'
alias reload='exec zsh' # a fresh shell: cleaner than `source ~/.zshrc`
alias zshbench='time zsh -i -c exit' # startup time, aim for < 0.3 s
alias zshprof='ZPROF=1 zsh -i -c exit' # which part of startup is slow
# ---- Git -------------------------------------------------------------------------
# Not defined here on purpose, the Oh My Zsh `git` plugin already has better ones:
# gst = status, gsb = status -sb (your old `gs`), glog = log --oneline --graph --decorate
# gl = git pull (your old `gl` would have overridden it), gp = push, gco, gcb, gaa, gcmsg
# Your old `gs` also hid Ghostscript, whose command is `gs`.
(( $+commands[lazygit] )) && alias lg='lazygit'
# ---- Local AI (Ollama + Continue) -------------------------------------------------
alias ai-on="$DOTFILES/scripts/ollama-start.sh" # set options, quit apps, load the model
alias ai-off="$DOTFILES/scripts/ollama-stop.sh" # unload the model, quit Ollama
alias ai-ps='ollama ps' # what is loaded, "100% GPU" is what you want
alias ai-ls='ollama list' # downloaded models
# ---- Clipboard (macOS: pbcopy/pbpaste, not xclip) -------------------------------------
alias copy='pbcopy' # git diff | copy
alias paste='pbpaste'
# ---- System ------------------------------------------------------------------------
alias df='df -h' # human-readable sizes
alias du1='du -h -d 1 | sort -hr' # size of each item in this folder, biggest first
alias mem='top -l 1 -s 0 | grep PhysMem' # macOS has no `free` command
alias ports='lsof -iTCP -sTCP:LISTEN -n -P' # what is listening on which port
alias path='print -l $path' # one PATH entry per line
alias flushdns='sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder'
alias myip='curl -s https://api.ipify.org; echo' # public IP (https, unlike the old http one)
alias localip='ipconfig getifaddr en0 || ipconfig getifaddr en1' # Mac Studio: en0 = Ethernet or Wi-Fi
alias grep='grep --color=auto'
alias cp='cp -i' # ask before overwriting
alias mv='mv -i'
# ---- Homebrew --------------------------------------------------------------------------
alias brewup='brew update && brew upgrade && brew cleanup'
alias brewdump='brew bundle dump --force --file="$DOTFILES/Brewfile"' # save installed apps into your repo
# ---- macOS -------------------------------------------------------------------------------
alias o='open'
alias oo='open .' # open this folder in Finder
alias ghostty-validate='ghostty +validate-config'
# ---- Files: preview with fzf + bat ----------------------------------------------------------
alias preview="fzf --preview 'bat --style=numbers --color=always {}'"
# ---- Optional, only if installed ---------------------------------------------------------------
if [[ -f ~/.config/myquotes/quotes ]] && (( $+commands[fortune] )); then
alias fortune='fortune ~/.config/myquotes/quotes' # your own quotes (needs: strfile quotes)
fi
(( $+commands[curseradio] )) && alias radio='curseradio'
If you want to know more about cat, exa, fzf, youtube-dl, and ripgrep, read our article Awesome command linux tools: bat, fzf, exa, ripgrep.
# =============================================================================
# ~/.zsh_functions (stow package: zsh/.zsh_functions)
# A function is a small program living in your shell. Unlike an alias it can take arguments
# anywhere ("$1", "$@"), use variables and make decisions.
# =============================================================================
# mkcd dir: create a folder (and parents) and go into it.
mkcd() {
[[ -n $1 ]] || { echo "usage: mkcd " ; return 1; }
mkdir -p -- "$1" && cd -- "$1"
}
# calc "3*4" -> 12 (bc -l = math library, decimals work: calc "10/3")
calc() { bc -l <<< "$*" }
alias calc='noglob calc' # noglob: `calc 3*4` also works without quotes
# urlencode "hello world" -> hello+world
urlencode() {
python3 -c 'import sys, urllib.parse as u; print(u.quote_plus(" ".join(sys.argv[1:])))' "$@"
}
# myripgrep : search inside my Hugo content folder.
myripgrep() { rg "$@" "$HOME/JustToThePoint/content/" }
# mysearch : same search, and open the same words in Google
# (macOS: `open`, there is no xdg-open).
mysearch() {
rg "$@" "$HOME/JustToThePoint/content/"
open "https://www.google.com/search?q=$(urlencode "$*")"
}
# searchEdit: fuzzy-find a config/script file with a preview, then open it in $EDITOR.
# Handles spaces in names (-print0 / --read0 / xargs -0) and skips folders that do not exist.
searchEdit() {
local -a dirs existing
local d
dirs=("$HOME/.config" "$DOTFILES" "$HOME/.local/bin" "$HOME/Dropbox")
for d in $dirs; do [[ -d $d ]] && existing+=("$d"); done
(( ${#existing} )) || { echo "searchEdit: no folder to search"; return 1; }
find "${existing[@]}" -type f -not -path '*/.git/*' -print0 \
| fzf --read0 --print0 --preview 'bat --style=numbers --color=always {}' \
| xargs -0 -o ${=EDITOR}
}
# ytmp3 : download the audio of a video as mp3 into ~/Music.
# youtube-dl is unmaintained, yt-dlp replaces it (brew install yt-dlp ffmpeg).
# -P sets the download folder, so your shell stays where it is (the old version did cd).
ytmp3() {
(( $+commands[yt-dlp] )) || { echo "brew install yt-dlp ffmpeg"; return 1; }
yt-dlp --extract-audio --audio-format mp3 -P "$HOME/Music" -o '%(title)s.%(ext)s' "$@"
}
# playmp3: play all mp3 files in ~/Music in random order (brew install mpv). Quit with q.
playmp3() {
(( $+commands[mpv] )) || { echo "brew install mpv"; return 1; }
mpv --no-video --shuffle "$HOME"/Music/*.mp3
}
# free: memory summary (macOS has no `free`). Useful to check before loading a big model.
free() {
top -l 1 -s 0 | grep PhysMem
memory_pressure | grep 'free percentage'
}
# killport : stop whatever is listening on that port (a dev server that did not exit).
killport() {
[[ -n $1 ]] || { echo "usage: killport " ; return 1; }
local pids
pids=$(lsof -ti tcp:"$1" -sTCP:LISTEN) || { echo "nothing is listening on port $1"; return 1; }
echo "killing PID(s): ${pids//$'\n'/ }"
echo "$pids" | xargs kill
}
# weather [city]: one-line weather from wttr.in
weather() { curl -s "wttr.in/${1:-}?format=3" }
# Archives: the Oh My Zsh `extract` plugin gives you `extract ` and `x `.
fish is a smart and user-friendly command line shell.
neofetch
# Aliases
alias efish 'vim ~/.config/fish/config.fish'
alias evim 'vim ~/.vimrc'
alias cat 'bat'
alias rfish 'source ~/.config/fish/config.fish'
alias tobash="sudo chsh $USER -s /bin/bash && echo 'Now log out.'"
function fish_greeting
fortune | cowsay
end
# PROMPT: https://fishshell.com/docs/current/cmds/fish_prompt.html
function fish_prompt -d "Write out the prompt"
# This shows up as USER@HOST /home/user/ >, with the directory colored
# $USER and $hostname are set by fish, so you can just use them
# instead of using `whoami` and `hostname`
printf '%s@%s %s%s%s > ' $USER $hostname \
(set_color $fish_color_cwd) (prompt_pwd) (set_color normal)
end