# Shell Mastery Reference Tricks from Usenet archives, Bell Labs culture, and 30+ years of terminal living. ## Readline Kill Ring Readline maintains a ring buffer of killed text: - `Ctrl-w` -- kill word backward - `Ctrl-k` -- kill to end of line - `Ctrl-u` -- kill to beginning of line - `Ctrl-y` -- yank (paste) most recent kill - `Alt-y` -- cycle through kill ring after a yank (the trick nobody knows) - `Alt-3 Alt-b` -- numeric argument: move backward 3 words Targeted completions: - `Ctrl-x ~` -- username completion - `Ctrl-x /` -- filename completion - `Ctrl-x $` -- variable name completion - `Ctrl-x @` -- hostname completion - `Ctrl-x !` -- command name completion ## fc (Fix Command) - `fc` -- open last command in $EDITOR, execute on save - `fc -3` -- open 3 commands back - `fc 100 110` -- edit commands 100-110 as a batch script - `fc -s old=new` -- re-run last command with substitution - `fc -l -20` -- list last 20 commands Alias: `r='fc -s'` then `r cc` re-runs last command starting with "cc" ## Ctrl-x Ctrl-e Opens current half-typed command in $EDITOR. Different from fc: - fc edits the PREVIOUS command after it ran - Ctrl-x Ctrl-e edits the CURRENT command before execution - In vi mode: hit Escape then v ## History Expansion Event designators: - `!!` -- last command - `!-3` -- 3 commands ago - `!grep` -- most recent command starting with "grep" - `!?config?` -- most recent containing "config" Word designators: - `!$` -- last argument of previous command - `!^` -- first argument - `!*` -- all arguments - `!!:2` -- second argument - `!!:2-4` -- arguments 2 through 4 Modifiers (chain these): - `:h` -- head (dirname): /a/b/c.txt -> /a/b - `:t` -- tail (basename): /a/b/c.txt -> c.txt - `:r` -- root (strip extension): file.tar.gz -> file.tar - `:e` -- extension only: file.tar.gz -> .gz - `:p` -- print but don't execute - `:s/old/new/` -- substitute first - `:gs/old/new/` -- global substitute Killer combos: ``` !!:gs/src/build/ # rerun with all src -> build cd !$:h # cd to dirname of last arg ``` ## Alt-. (Insert Last Argument) Press `Alt-.` to insert the last argument of the previous command. Repeat to cycle through older commands' last arguments. This is the single most useful keystroke for avoiding path retyping. ## Process Substitution ```bash diff <(sort file1) <(sort file2) # diff two commands diff <(ssh server cat /etc/hosts) /etc/hosts # compare remote vs local comm -23 <(sort deployed) <(sort healthy) # set difference # Variable survival (no subshell): while read -r line; do count=$((count+1)); done < <(grep ERROR log) # vs pipe (subshell, variables lost): grep ERROR log | while read -r line; do count=$((count+1)); done # Fan-out with tee: cmd | tee >(gzip > backup.gz) >(jq . > pretty.json) > /dev/null ``` ## Named Pipes (FIFOs) ```bash mkfifo /tmp/pipe # Terminal 1: tail -f /var/log/app.log > /tmp/pipe # Terminal 2: grep ERROR < /tmp/pipe # Skip temp file for large data: mkfifo /tmp/dbpipe gunzip -c dump.sql.gz > /tmp/dbpipe & mysql mydb < /tmp/dbpipe # Persistent fd (avoids blocking-on-open): mkfifo /tmp/pipe exec 3<>/tmp/pipe echo "msg" >&3 read -r line <&3 ``` ## File Descriptor Tricks ```bash # Swap stdout and stderr: command 3>&1 1>&2 2>&3 # Persistent stderr redirect: exec 2>error.log # ALL subsequent stderr goes to file # Bash TCP sockets (no netcat): exec 3<>/dev/tcp/example.com/80 echo -e "GET / HTTP/1.0\r\n\r\n" >&3 cat <&3 exec 3>&- # Close fd to prevent child inheritance: some_command 3<&- ``` ## Signal Traps ```bash # The definitive cleanup pattern: tempfile=$(mktemp) || exit trap 'rm -f "$tempfile"' EXIT # fires on ANY exit # Correct SIGINT handler (preserves caller semantics): trap 'rm -f "$tempfile"; trap - INT; kill -s INT "$$"' INT # Daemon reload: trap 'read_config' HUP # Background process reaping: trap '[[ $pid ]] && kill "$pid"' EXIT long_cmd & pid=$! wait "$pid" ``` ## LC_ALL=C for Performance ```bash export LC_ALL=C # before sort/grep/awk on large files # Bypasses locale-aware Unicode processing. 5-10x faster. ``` ## PIPESTATUS ```bash false | true | false echo ${PIPESTATUS[@]} # "1 0 1" -- every pipe stage's exit code ``` ## ed for Scripted Editing POSIX guaranteed. Preserves inodes (unlike sed -i). ```bash # Uncomment a line: ed -s file.conf <<'EOF' /^#ServerName/s/^#// w q EOF # Insert after a pattern: ed -s file <<'EOF' /pattern/a new line here . w q EOF # Delete matching lines: printf '%s\n' 'g/pattern/d' w q | ed -s file ``` ## Brace Expansion ```bash cp file.txt{,.bak} # backup mv file.{old,new} # rename mkdir -p app/{src,lib,test,doc} # scaffold diff config.{orig,modified} # quick diff echo {01..10} # zero-padded sequence echo {a,b}{1,2} # cartesian: a1 a2 b1 b2 touch test_{a,b,c}_{1..5}.txt # 15 files ``` ## Parameter Expansion ```bash ${var:-default} # use default if unset ${var:=default} # assign default if unset ${var:+alternate} # use alternate if set ${var:?error} # exit with error if unset ${#var} # string length ${var:5:3} # substring ${var#*/} # strip shortest prefix ${var##*/} # strip longest prefix (basename) ${var%/*} # strip shortest suffix (dirname) ${var%%.*} # strip longest suffix (all extensions) ${var/old/new} # replace first ${var//old/new} # replace all ${var^^} # UPPERCASE ${var,,} # lowercase ${!name} # indirect: value of variable named by $name ``` ## shopt Options That Matter ```bash shopt -s autocd # type dir name to cd into it shopt -s cdspell # auto-correct cd typos shopt -s dirspell # auto-correct tab completion typos shopt -s globstar # enable ** recursive glob shopt -s histverify # !! shows expansion before executing shopt -s histappend # append to history, don't overwrite shopt -s extglob # enable !(pattern), +(pattern), etc. shopt -s nullglob # unmatched globs expand to nothing ``` ## CDPATH ```bash export CDPATH=".:~:~/dev" cd srice # jumps to ~/dev/srice from anywhere ``` ## HISTCONTROL ```bash HISTCONTROL='ignorespace:erasedups' # dedup entire history HISTSIZE=1000000 HISTFILESIZE=1000000 HISTTIMEFORMAT="%F %T " # ISO timestamps ``` ## PROMPT_COMMAND and trap DEBUG ```bash # Sync history across terminals: PROMPT_COMMAND='history -a; history -c; history -r' # Pre-command timer: trap 'timer_start' DEBUG PROMPT_COMMAND='timer_stop' ``` ## Vi Mode Deep Tricks ```bash set -o vi # In command mode: # k/j -- history navigation # /string -- search history # # -- comment out current line and save to history # v -- open in $EDITOR ``` The `#` trick: type command, Escape, `#` -- saves it commented in history. Do other work. Recall with `k`, `0x` to uncomment, Enter. ## Zsh-Specific Power Suffix aliases: ```zsh alias -s py=nvim # typing "script.py" opens in nvim alias -s md=glow # typing "README.md" renders it ``` Global aliases: ```zsh alias -g G='| grep' alias -g L='| less' alias -g J='| jq .' alias -g C='| wc -l' ``` Named directories: ```zsh hash -d dev=~/dev hash -d ky=~/dev/krisyotam.com cd ~dev # jumps to ~/dev ``` Hook functions: `precmd`, `preexec`, `chpwd` (auto-ls after cd)