The Terminal
Terminal Usage Manual
The terminal is an instrument of precision. Mastery is defined not by a library of memorized commands, but by the economy of movement. An expert operates at the speed of thought, navigating history, expanding paths, and manipulating the buffer without ever lifting their hands from the home row.
This manual documents the layers of terminal interaction: Readline keybindings (portable shortcuts for Bash, Python, and SQL), Shell features (expansion, history, and job control), and Control sequences. These skills represent the bridge between basic CLI usage and professional automation.
1. Readline – Line Editing Shortcuts
Readline is the library that provides interactive line editing in Bash, Zsh, Python’s REPL, MySQL/psql clients, GDB, and most other interactive CLIs. Learning readline shortcuts is learning a skill that transfers across every tool simultaneously.
The keymap is derived from Emacs. An alternative vi mode exists (documented in §2).
Cursor Movement
Ctrl+A go to start of line
Ctrl+E go to end of line
Ctrl+F move forward one character (same as →)
Ctrl+B move backward one character (same as ←)
Alt+F move forward one word
Alt+B move backward one word
Ctrl+XX toggle between start of line and current position
Deleting Text
Ctrl+D delete character under cursor (or EOF if line is empty)
Backspace delete character before cursor
Ctrl+H same as Backspace
Ctrl+K kill (cut) from cursor to end of line
Ctrl+U kill from cursor to start of line
Alt+D kill from cursor to end of word (forward)
Ctrl+W kill word before cursor (backward, whitespace-delimited)
Alt+Backspace kill word before cursor (backward, word-delimited)
Ctrl+Y yank (paste) the most recently killed text
Alt+Y rotate through kill ring (after Ctrl+Y, cycle through previous kills)
The “kill ring” is readline’s clipboard. Multiple kill operations accumulate in it. Ctrl+Y pulls the most recent; Alt+Y (after Ctrl+Y) cycles through older entries.
Modifying Text
Ctrl+T transpose characters (swap current and previous)
Alt+T transpose words (swap current and previous word)
Alt+U uppercase from cursor to end of word
Alt+L lowercase from cursor to end of word
Alt+C capitalise the word at/after cursor
Ctrl+_ undo last change (can repeat)
Alt+R revert all changes to current line (restore from history)
Ctrl+V insert next character literally (verbatim insert -- useful for tabs)
Completion
Tab auto-complete command, file, or variable name
Tab Tab show all completions (when multiple options exist)
Alt+= list completions (same as Tab Tab)
Alt+? list completions (same as Tab Tab)
Alt+* insert all completions into line at once
Ctrl+X / list filename completions
Ctrl+X $ list variable completions
Ctrl+X @ list hostname completions
Ctrl+X ! list command completions
History Navigation
↑ / Ctrl+P previous command in history
↓ / Ctrl+N next command in history
Alt+< first command in history
Alt+> last command (most recent, also clears to empty)
Ctrl+R reverse incremental search through history
Ctrl+S forward incremental search (may conflict with flow control -- see §6)
Ctrl+G cancel history search (return to current line unchanged)
Ctrl+O execute current line from history, then advance to next line
Alt+. insert last argument of previous command (repeat to go further back)
Alt+_ same as Alt+.
Ctrl+R in Detail
Ctrl+R is one of the most used shortcuts in daily terminal work.
Ctrl+R open reverse history search (bck-i-search: prompt appears)
type chars narrows results as you type
Ctrl+R cycle to next older match
Ctrl+S cycle to next newer match (if flow control disabled)
Enter execute the found command
→ or Ctrl+E bring command to prompt without executing
Ctrl+G cancel, restore original line
Esc cancel, keep found command on prompt
2. Vi Mode in the Shell
Enable vi mode in Bash:
set -o vi
In Zsh:
bindkey -v
In vi mode, the shell starts in insert mode. Esc enters normal mode with Vim-like navigation.
Vi Mode – Insert Mode
Esc / Ctrl+[ switch to normal (command) mode
Backspace delete character before cursor
Ctrl+W delete word before cursor
Ctrl+U delete to start of line
Ctrl+A go to start of line (also works in vi insert mode in Bash)
Ctrl+E go to end of line
Vi Mode – Normal Mode
h / l move left / right
w / b / e next word / previous word / end of word
0 / ^ start of line / first non-blank
$ end of line
i / I insert before cursor / insert at start of line
a / A append after cursor / append at end of line
x delete character under cursor
dw delete word
d$ / D delete to end of line
dd delete entire line → replace with history
u undo
k / j previous / next history entry
/ search history forward
? search history backward
n / N next / previous search result
. repeat last change
cc / C change line / change to end of line
r replace character under cursor
~ toggle case
v edit in $EDITOR (full Vim session on the current line)
The v command in vi normal mode is extremely useful: it opens the current line in your $EDITOR (Neovim if configured), lets you edit it with full editor power, and executes it when you save and quit.
3. History
history Command
history # show full history
history 20 # last 20 entries
history | grep "nginx" # search history
history | tail -20 # recent entries
history -c # clear history
history -w # write history buffer to file
history -r # read history file into buffer
history -d 42 # delete entry 42
History Expansion (!-based)
!! last command (re-run it)
!42 run command number 42
!-2 run second-to-last command
!nginx run most recent command starting with "nginx"
!?keyword run most recent command containing "keyword"
!$ last argument of last command
!^ first argument of last command
!* all arguments of last command (except command name)
!:2 second argument of last command
!:2-4 arguments 2 through 4 of last command
# Modify before running
!!:s/old/new/ substitute in last command
!nginx:s/reload/stop/ substitute in most recent nginx command
# Show without executing (add :p)
!!:p print last command without running it
!42:p print command 42 without running it
Examples:
sudo !! # re-run last command with sudo
cd !$ # cd to last argument of previous command
vim !$ # open last argument in vim
History Configuration
In ~/.bashrc or ~/.zshrc:
# Bash
HISTSIZE=100000 # lines in memory
HISTFILESIZE=200000 # lines on disk
HISTFILE=~/.bash_history
HISTCONTROL=ignoredups:ignorespace # ignore duplicate lines and lines starting with space
HISTIGNORE="ls:ll:la:cd:pwd:clear:exit:history" # don't record these
shopt -s histappend # append to history file, don't overwrite
shopt -s cmdhist # save multi-line commands as single entry
HISTTIMEFORMAT="%F %T " # prepend timestamp to history entries
# Zsh equivalent
HISTSIZE=100000
SAVEHIST=100000
HISTFILE=~/.zsh_history
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_SPACE
setopt HIST_APPEND
setopt SHARE_HISTORY # share history across all zsh sessions
setopt EXTENDED_HISTORY # save timestamp and duration
4. Tab Completion
Bash Completion
Install bash-completion for extended completions:
apk add bash-completion # Alpine
sudo pacman -S bash-completion # Arch
sudo apt install bash-completion # Ubuntu
sudo dnf install bash-completion # Rocky
Add to ~/.bashrc:
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
fi
Completion Behaviour
Tab complete unique match immediately
Tab Tab list all completions when ambiguous
Alt+* insert all completions at cursor
Programmatic Completion
complete -W "start stop restart reload status" systemctl
complete -f -X '!*.txt' my-text-tool # only complete .txt files
Zsh Completion (compinit)
autoload -Uz compinit && compinit
# Case-insensitive completion
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
# Menu-style completion
zstyle ':completion:*' menu select
# Group completions by type
zstyle ':completion:*' group-name ''
zstyle ':completion:*:descriptions' format '%F{yellow}-- %d --%f'
5. Shell Expansions
Understanding expansions is understanding how the shell processes a command line before executing it.
Brace Expansion
echo {a,b,c} # a b c
echo {1..5} # 1 2 3 4 5
echo {1..10..2} # 1 3 5 7 9 (step by 2)
echo {a..z} # a b c ... z
echo {A..Z} # A B C ... Z
echo file{1..10}.txt # file1.txt file2.txt ... file10.txt
mkdir -p project/{src,tests,docs,scripts}
cp file.txt{,.bak} # cp file.txt file.txt.bak (quick backup)
mv file.{old,new} # mv file.old file.new (quick rename)
echo {Jan,Feb,Mar}_{2025,2026} # Jan_2025 Jan_2026 Feb_2025 Feb_2026 ...
Tilde Expansion
~ # $HOME
~username # username's home
~+ # $PWD (current directory)
~- # $OLDPWD (previous directory)
Parameter Expansion
$variable # basic expansion
${variable} # same, with explicit boundary
${#variable} # length of value
# Default values
${var:-default} # use default if var is unset or empty
${var:=default} # use default AND assign if var is unset or empty
${var:+other} # use other if var IS set (inverse)
${var:?error message} # exit with error if var is unset
# Substring
${var:offset} # substring from offset
${var:offset:length} # substring from offset, length chars
# Case modification
${var^} # first char uppercase
${var^^} # all chars uppercase
${var,} # first char lowercase
${var,,} # all chars lowercase
# Pattern removal
${var#pattern} # remove shortest match from start
${var##pattern} # remove longest match from start
${var%pattern} # remove shortest match from end
${var%%pattern} # remove longest match from end
# Replacement
${var/pattern/replacement} # replace first occurrence
${var//pattern/replacement} # replace all occurrences
${var/#pattern/replacement} # replace if pattern matches at start
${var/%pattern/replacement} # replace if pattern matches at end
# Indirection
${!name} # value of variable whose name is in $name
Examples:
file="backup_2026-04-09.tar.gz"
echo ${file%.tar.gz} # backup_2026-04-09 (strip extension)
echo ${file#backup_} # 2026-04-09.tar.gz (strip prefix)
echo ${file^^} # BACKUP_2026-04-09.TAR.GZ
path="/usr/local/bin/nginx"
echo ${path##*/} # nginx (basename)
echo ${path%/*} # /usr/local/bin (dirname)
# Rename all .txt to .md using parameter expansion
for f in *.txt; do
mv "$f" "${f%.txt}.md"
done
Command Substitution
$(command) # preferred form
`command` # legacy form
output=$(ls -la)
date=$(date +%F)
count=$(find . -name "*.py" | wc -l)
files=$(find /etc -name "*.conf" -type f)
Arithmetic Expansion
$((expression))
echo $((2 + 3)) # 5
echo $((10 / 3)) # 3 (integer division)
echo $((10 % 3)) # 1 (modulo)
echo $((2 ** 10)) # 1024 (exponentiation)
i=$((i + 1))
echo $((16#ff)) # 255 (hex to decimal)
echo $((2#1010)) # 10 (binary to decimal)
Process Substitution
diff <(sort file1.txt) <(sort file2.txt)
comm -23 <(sort file1.txt) <(sort file2.txt)
wc -l <(find / -name "*.conf" 2>/dev/null)
while read line; do ...; done < <(command)
Word Splitting and Globbing
# Globbing
* match any string (including empty)
? match any single character
[abc] match a, b, or c
[a-z] match any lowercase letter
[!abc] match anything except a, b, c
{*.txt,*.md} brace + glob
# Extended globs (shopt -s extglob in bash)
+(pattern) one or more occurrences
*(pattern) zero or more occurrences
?(pattern) zero or one occurrence
@(pattern) exactly one
!(pattern) anything except pattern
# Globstar (shopt -s globstar)
** match any path, including subdirectories
ls **/*.py # all .py files recursively
Enable extended globbing:
shopt -s extglob # bash
shopt -s globstar # bash
# Zsh has them on by default with:
setopt extended_glob
setopt glob_star_short
6. Job Control
Foreground / Background
command & # run in background from the start
command # run in foreground
Ctrl+Z # suspend foreground process (SIGTSTP)
bg # resume most recent suspended job in background
bg %2 # resume job #2 in background
fg # bring most recent background job to foreground
fg %2 # bring job #2 to foreground
Jobs
jobs # list current shell's background jobs
jobs -l # include PIDs
jobs -r # running jobs only
jobs -s # stopped jobs only
Job state codes:
Running actively running in background
Stopped suspended (Ctrl+Z)
Done completed
Disown
disown removes a job from the shell’s job table so it isn’t killed when the shell exits.
disown %1 # disown job 1
disown -a # disown all jobs
disown -h %1 # disown but still receive HUP signal
Typical pattern for long-running commands you want to survive logout:
some-long-command &
disown $!
Or use nohup:
nohup some-long-command > output.log 2>&1 &
Flow Control (Ctrl+S / Ctrl+Q)
Ctrl+S pause terminal output (XOFF -- freezes output scrolling)
Ctrl+Q resume terminal output (XON)
Ctrl+S is often accidentally pressed and causes the terminal to appear frozen. The fix is always Ctrl+Q. Disable it permanently to prevent conflicts with readline’s Ctrl+S forward search:
stty -ixon # disable flow control (add to .bashrc)
7. Terminal Control Sequences
These work in most terminal emulators (alacritty, kitty, st, gnome-terminal, etc.).
Screen Control
Ctrl+L clear screen (equivalent to running `clear`)
clear clear screen
reset reset terminal state (fixes garbled output)
Ctrl+C send SIGINT (interrupt current process)
Ctrl+D send EOF (exit shell if line is empty, delete char if not)
Ctrl+Z send SIGTSTP (suspend current process)
Ctrl+\ send SIGQUIT (quit + core dump)
Scroll
Shift+PageUp scroll up (terminal emulator history, not shell history)
Shift+PageDown scroll down
Shift+↑ scroll up one line
Shift+↓ scroll down one line
Terminal Resize
stty size # print current rows and columns
stty rows 50 cols 220 # set terminal size manually
echo $LINES $COLUMNS # current terminal dimensions
tput lines # lines via terminfo
tput cols # columns via terminfo
Terminal Reset
When a command outputs binary data or escape sequences and garbles your terminal:
reset # full reset
tput reset # same via terminfo
echo -e "\033c" # ESC c (full reset sequence)
printf '\033[2J\033[H' # clear screen and move cursor to top-left
stty sane # restore sane terminal settings without clearing
8. Shell Configuration Files
Bash Load Order
Interactive login shell: /etc/profile → ~/.bash_profile (or ~/.bash_login or ~/.profile)
Interactive non-login shell: /etc/bash.bashrc → ~/.bashrc
Non-interactive: $BASH_ENV file
In practice: put everything in ~/.bashrc and source it from ~/.bash_profile:
# ~/.bash_profile
-f ~/.bashrc && . ~/.bashrc
Zsh Load Order
Login shell: /etc/zsh/zprofile → ~/.zprofile → /etc/zsh/zshrc → ~/.zshrc → ~/.zlogin
Interactive: /etc/zsh/zshrc → ~/.zshrc
Non-interactive: none (unless $ZDOTDIR is set)
A Well-Structured ~/.bashrc
# ~/.bashrc -- loaded for every interactive non-login shell
# Exit if non-interactive
$- != *i* && return
# --- History ---
HISTSIZE=100000
HISTFILESIZE=200000
HISTCONTROL=ignoredups:ignorespace
HISTTIMEFORMAT="%F %T "
HISTIGNORE="ls:ll:cd:pwd:exit:clear"
shopt -s histappend
shopt -s cmdhist
# --- Shell Options ---
shopt -s globstar # ** recursive glob
shopt -s extglob # extended patterns
shopt -s nocaseglob # case-insensitive globbing
shopt -s autocd # type directory name to cd into it
shopt -s checkwinsize # update LINES/COLUMNS after commands
shopt -s dirspell # spell-correct directory names on cd
shopt -s cdspell # spell-correct cd arguments
set -o noclobber # prevent > from overwriting files (use >| to force)
stty -ixon # disable Ctrl+S flow control
# --- Prompt ---
# Basic colour prompt
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
# --- Environment ---
export EDITOR=nvim
export VISUAL=nvim
export PAGER=less
export LESS='-R --quit-if-one-screen --no-init'
export LANG=en_GB.UTF-8
export PATH="$HOME/.local/bin:$HOME/bin:$PATH"
# --- Aliases ---
alias ls='ls --color=auto'
alias ll='ls -lh'
alias la='ls -lah'
alias lt='ls -lht'
alias l='ls -CF'
alias grep='grep --color=auto'
alias diff='diff --color=auto'
alias ip='ip --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias cp='cp -iv'
alias mv='mv -iv'
alias rm='rm -Iv' # interactive for 3+ files
alias mkdir='mkdir -pv'
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias ~='cd ~'
alias c='clear'
alias h='history'
alias j='jobs -l'
alias e='$EDITOR'
alias sv='sudo nvim'
# Git aliases
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline --graph --all --decorate'
alias gd='git diff'
# Safety
alias chown='chown --preserve-root'
alias chmod='chmod --preserve-root'
alias chgrp='chgrp --preserve-root'
# --- Functions ---
# Make a directory and cd into it
mkcd() { mkdir -p "$1" && cd "$1"; }
# Extract any archive
extract() {
if [ -f "$1" ]; then
case "$1" in
*.tar.bz2) tar xjf "$1" ;;
*.tar.gz) tar xzf "$1" ;;
*.tar.xz) tar xJf "$1" ;;
*.tar.zst) tar --zstd -xf "$1" ;;
*.tar) tar xf "$1" ;;
*.bz2) bunzip2 "$1" ;;
*.gz) gunzip "$1" ;;
*.xz) unxz "$1" ;;
*.zip) unzip "$1" ;;
*.7z) 7z x "$1" ;;
*.rar) unrar x "$1" ;;
*) echo "Cannot extract '$1'" ;;
esac
else
echo "'$1' is not a file"
fi
}
# Quick find
ff() { find . -type f -iname "*$1*"; }
fd() { find . -type d -iname "*$1*"; }
# Show PATH entries one per line
path() { echo "$PATH" | tr ':' '\n'; }
# Reload bashrc
reload() { source ~/.bashrc && echo "Reloaded ~/.bashrc"; }
# --- Completion ---
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
9. Useful Shell Built-ins
echo "text" # print text
printf "fmt" args # formatted print (like C printf)
printf "%-10s %5d\n" "key" 42
read var # read stdin into variable
read -p "Enter: " var # with prompt
read -s var # silent (passwords)
read -t 10 var # timeout after 10 seconds
read -a arr # read into array
read -r line # raw (don't interpret backslashes)
type cmd # show type of command (alias, builtin, file)
which cmd # show path of executable
command cmd # run cmd bypassing aliases/functions
builtin cd # run builtin directly
hash # show hashed command paths
hash -r # clear hash table (refresh $PATH cache)
alias # list all aliases
alias name='cmd' # define alias
unalias name # remove alias
export VAR=val # export variable to environment
unset VAR # remove variable
declare -a arr # declare array
declare -A map # declare associative array
declare -r CONST=val # read-only
declare -i NUM=5 # integer
declare -x VAR=val # same as export
declare -f func_name # print function definition
declare -F # list all function names
local var=val # local variable (inside functions only)
source file / . file # run file in current shell
exec command # replace current shell with command
exit N # exit with code N
trap cmd SIGNAL # run cmd when signal received
trap 'cleanup' EXIT # cleanup on exit
trap 'err_handler' ERR # run on any error
set -e # exit on error
set -u # error on undefined variable
set -x # print each command before running (debug)
set -o pipefail # fail if any command in pipe fails
set +e # disable exit on error
ulimit -n 65535 # set max open files
ulimit -u 1000 # set max processes
ulimit -a # show all limits
umask 022 # set default file permissions mask
true / false # always succeed / always fail
: nothing # no-op (always returns 0)
wait # wait for all background jobs
wait PID # wait for specific PID
sleep 5 # sleep 5 seconds
sleep 0.5 # sleep 500ms
test -f file # test conditions (POSIX)
[ -f file ] # same ([ is a builtin, not a bracket)
-f file # extended test (bash/zsh -- no word splitting)
10. Variables and Arrays
Variables
var="hello" # assign (no spaces around =)
echo "$var" # always quote variable expansions
echo "${var}world" # explicit boundary
readonly CONST="value" # read-only variable
unset var # delete variable
env # show all environment variables
printenv VAR # print one variable
export VAR="value" # make available to child processes
Indexed Arrays
arr=(a b c d e) # declare and populate
arr[0]="a" # set element
echo ${arr[0]} # access element
echo ${arr[@]} # all elements
echo ${arr[*]} # all elements (no word splitting protection)
echo ${#arr[@]} # array length
echo ${!arr[@]} # all indices
arr+=(f g) # append
unset arr[2] # delete element
unset arr # delete array
# Slice
echo ${arr[@]:1:3} # 3 elements starting at index 1
# Iterate
for item in "${arr[@]}"; do
echo "$item"
done
Associative Arrays (Bash 4+)
declare -A map
map[key1]="value1"
map[key2]="value2"
echo ${map[key1]} # value1
echo ${!map[@]} # all keys
echo ${map[@]} # all values
echo ${#map[@]} # number of entries
for key in "${!map[@]}"; do
echo "$key → ${map[$key]}"
done
11. Input / Output Redirection
> redirect stdout (overwrite)
>> redirect stdout (append)
< redirect stdin
<< here-document
<<< here-string
2> redirect stderr
2>> redirect stderr (append)
2>&1 redirect stderr to stdout
&> redirect both (bash)
>& redirect both (alternate)
| pipe stdout to stdin of next command
|& pipe both stdout and stderr (bash 4+)
# Redirect
cmd > out.txt 2> err.txt # separate stdout and stderr
cmd > out.txt 2>&1 # combine: stderr goes to same file as stdout
cmd 2>&1 | tee out.txt # show on screen AND save
cmd > /dev/null 2>&1 # discard everything
cmd > out.txt 2>/dev/null # save stdout, discard stderr
# Append
cmd >> existing.log 2>&1
# Here-doc
cat << 'EOF'
content here
EOF
# Here-string
base64 <<< "encode this"
grep "pattern" <<< "$variable"
# Tee
cmd | tee out.txt # stdout to file AND screen
cmd | tee -a out.txt # append to file AND screen
cmd | tee file1 file2 # write to multiple files
cmd | tee >(process1) >(process2) | process3 # tee to processes
12. Pipes and Pipelines
cmd1 | cmd2 # pipe stdout of cmd1 to stdin of cmd2
cmd1 |& cmd2 # pipe stdout+stderr (bash 4+)
cmd1 | cmd2 | cmd3 # chain
# Logical operators
cmd1 && cmd2 # run cmd2 only if cmd1 succeeds
cmd1 || cmd2 # run cmd2 only if cmd1 fails
cmd1 ; cmd2 # run cmd2 regardless
# Groups
(cmd1 ; cmd2) | cmd3 # group in subshell, pipe combined output
{ cmd1; cmd2; } | cmd3 # group in current shell
# Named pipes (FIFOs)
mkfifo /tmp/mypipe
cmd1 > /tmp/mypipe &
cmd2 < /tmp/mypipe
Useful Pipeline Patterns
# Count something
ls | wc -l
find . -name "*.py" | wc -l
# Top 10 biggest files
du -ah . | sort -rh | head -10
# Most used commands from history
history | awk '{print $4}' | sort | uniq -c | sort -rn | head -10
# Running processes sorted by memory
ps aux --sort=-%mem | head -20
# Find and edit
grep -rl "old_string" . | xargs nvim
# Live log monitoring with filtering
tail -f /var/log/nginx/error.log | grep --line-buffered "crit\|error"
# Monitor a command's output
watch -n 1 'ss -tlnp | grep :80'
# Convert output to columns
cat /etc/passwd | cut -d: -f1,3,7 | column -t -s:
13. Scripting Essentials
Shebang and Strict Mode
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# set -e: exit on error
# set -u: error on undefined variable
# set -o pipefail: catch errors in pipelines
# IFS: safer word splitting
Conditionals
if -f "$file" ; then
echo "file exists"
elif -d "$file" ; then
echo "is a directory"
else
echo "not found"
fi
# One-liner
-f "$file" && echo "exists" || echo "missing"
# Case statement
case "$var" in
start) start_service ;;
stop) stop_service ;;
restart) stop_service; start_service ;;
*) echo "Usage: $0 {start|stop|restart}" ;;
esac
Test Conditions
# Files
-f file file exists and is a regular file
-d file file exists and is a directory
-l file file exists and is a symlink
-e file file exists (any type)
-r file file is readable
-w file file is writable
-x file file is executable
-s file file exists and is non-empty
-z file file exists and is empty
file1 -nt file2 file1 is newer than file2
file1 -ot file2 file1 is older than file2
# Strings
-z "$str" string is empty
-n "$str" string is non-empty
"$a" = "$b" strings are equal
"$a" != "$b" strings are not equal
"$a" < "$b" a comes before b (lexicographic -- in )
"$a" =~ regex a matches regex (only in )
# Integers
$a -eq $b equal
$a -ne $b not equal
$a -lt $b less than
$a -le $b less than or equal
$a -gt $b greater than
$a -ge $b greater than or equal
# Logical
! condition not
cond1 && cond2 and (in )
cond1 || cond2 or (in )
-a and (in [ ] -- old style, avoid)
-o or (in [ ] -- old style, avoid)
Loops
# For loop
for i in {1..10}; do echo $i; done
for file in *.txt; do echo "$file"; done
for i in $(seq 1 5); do echo $i; done
# C-style for
for ((i=0; i<10; i++)); do echo $i; done
# While loop
while condition ; do
...
done
# While read -- process file or command output line by line
while IFS= read -r line; do
echo "$line"
done < file.txt
# Process substitution version (avoids subshell)
while IFS= read -r line; do
echo "$line"
done < <(command)
# Until loop
until condition ; do
...
done
# Loop control
break # exit loop
continue # next iteration
break 2 # break out of 2 nested loops
Functions
my_function() {
local arg1="$1"
local arg2="$2"
echo "Args: $arg1, $arg2"
return 0
}
my_function "hello" "world"
result=$(my_function "a" "b") # capture output
14. Prompts
Bash PS1
# Components:
\u username
\h hostname (short)
\H hostname (full)
\w current directory (full path, ~ for home)
\W current directory (basename only)
\$ $ for normal user, # for root
\n newline
\t time (HH:MM:SS)
\T time (12-hour HH:MM:SS)
\d date (Weekday Month Day)
\j number of background jobs
\! history number
\# command number
# Colours (ANSI escape sequences -- must be wrapped in \[ \])
\[\033[0m\] reset
\[\033[1m\] bold
\[\033[01;30m\] bold black (dark grey)
\[\033[01;31m\] bold red
\[\033[01;32m\] bold green
\[\033[01;33m\] bold yellow
\[\033[01;34m\] bold blue
\[\033[01;35m\] bold magenta
\[\033[01;36m\] bold cyan
\[\033[01;37m\] bold white
# Example prompts
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
PS1='\[\033[01;36m\][\t]\[\033[00m\] \[\033[01;33m\]\u\[\033[00m\]@\h:\w\$ '
Starship (Cross-Shell Prompt)
Starship is a minimal, fast, highly customisable prompt written in Rust. It works identically in Bash, Zsh, and Fish.
# Install
curl -sS https://starship.rs/install.sh | sh
# Add to ~/.bashrc
eval "$(starship init bash)"
# Add to ~/.zshrc
eval "$(starship init zsh)"
Configure at ~/.config/starship.toml:
format = """
$username$hostname$directory$git_branch$git_status$cmd_duration$line_break$character"""
[character]
success_symbol = "[❯](bold green)"
error_symbol = "[❯](bold red)"
[directory]
truncation_length = 3
truncate_to_repo = true
[git_branch]
symbol = " "
[cmd_duration]
min_time = 500
format = "took [$duration](bold yellow)"
15. Terminal Multiplexer Quick Context
While tmux is documented fully in its own manual, these are the most immediate context-switching keys:
Ctrl+B d detach from session (session persists)
Ctrl+B [ enter copy/scroll mode
Ctrl+B z zoom current pane
Ctrl+B c new window
Ctrl+B n / p next / previous window
Run a command that survives terminal close without tmux:
nohup long-command &> output.log &
disown $!
16. Miscellaneous Productivity
# Repeat last command as root
sudo !!
# Run previous command with substitution
^old^new # replace first occurrence (Bash history expansion)
# Example: ran: git push origin mian
^mian^main # re-runs with correction
# Open current directory in file manager
xdg-open . # Linux (desktop environment)
open . # macOS
# Watch a command's output live
watch -n 1 'df -h'
watch -d 'cat /proc/loadavg' # -d highlights differences
# Parallel execution with &
cmd1 & cmd2 & cmd3 & wait # run 3 commands in parallel
# Time a command
time find / -name "*.conf" 2>/dev/null
# Measure command with more detail
{ time long-command; } 2>&1 | tail
# Execute something after a delay
sleep 300 && notify-send "Done"
# Run on disconnection (schedule a task in the future)
at now + 5 minutes <<< "touch /tmp/reminder"
# lsof -- list open files
lsof -i :8080 # what process is using port 8080
lsof -u dil # all files open by user dil
lsof /var/log/nginx/ # what process has this open
lsof +D /mnt/data # everything open in /mnt/data (blocking umount)
# fzf -- fuzzy finder (extremely useful)
# Install: pacman -S fzf / apt install fzf / dnf install fzf
Ctrl+R # fuzzy history search (replaces default if fzf is loaded)
Alt+C # fuzzy cd into subdirectory
Ctrl+T # fuzzy file path insert
# Add to .bashrc:
eval "$(fzf --bash)"
# xargs
echo "a b c" | xargs -n 1 echo # one arg per run
ls *.log | xargs -I{} mv {} /archive/{}
find . -name "*.old" | xargs rm -f
17. Quick Reference – Readline Cheatsheet
Movement:
Ctrl+A start of line Ctrl+E end of line
Alt+F forward word Alt+B backward word
Ctrl+F forward char Ctrl+B backward char
Delete:
Ctrl+K kill to end Ctrl+U kill to start
Alt+D kill word forward Ctrl+W kill word backward
Ctrl+D delete char Backsp delete before
Ctrl+Y yank (paste) Alt+Y rotate kill ring
Modify:
Ctrl+T transpose chars Alt+T transpose words
Alt+U uppercase word Alt+L lowercase word
Alt+C capitalise word Ctrl+_ undo
History:
↑/Ctrl+P previous ↓/Ctrl+N next
Ctrl+R search backward Ctrl+S search forward
Alt+. last argument !! last command
!$ last argument !^ first argument
!* all arguments
Control:
Ctrl+C SIGINT (interrupt) Ctrl+Z SIGTSTP (suspend)
Ctrl+D EOF / exit Ctrl+L clear screen
Ctrl+\ SIGQUIT Ctrl+S freeze output
Ctrl+Q unfreeze output