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