Bash#

Readline keybindings (default emacs mode) drive the Bash interactive line editor and most other readline consumers: the Python REPL, psql, mysql, gdb, ftp, sftp. Use set -o vi (or set editing-mode vi in ~/.inputrc) to switch to vi mode. stty -a lists the terminal-driver shortcuts (intr, susp, eof, erase); they sit underneath readline and apply even in dumb terminals.

Cursor Movement#

Key

Action

Ctrl-a

move to start of line

Ctrl-e

move to end of line

Ctrl-b

move back one character

Ctrl-f

move forward one character

Alt-b

move back one word

Alt-f

move forward one word

Ctrl-xx

jump between current position and start of line

Editing#

Key

Action

Ctrl-d

delete character under cursor (EOF on empty line)

Backspace

delete character before cursor

Ctrl-h

delete character before cursor (same as Backspace)

Alt-d

delete word forward

Ctrl-w

delete word backward (whitespace-delimited)

Alt-Backspace

delete word backward (non-alphanumeric-delimited)

Ctrl-k

cut from cursor to end of line

Ctrl-u

cut from cursor to start of line

Ctrl-y

paste (yank) last cut text

Alt-y

cycle through previous yanks after Ctrl-y

Ctrl-t

transpose character with previous

Alt-t

transpose word with previous

Alt-u

uppercase word from cursor

Alt-l

lowercase word from cursor

Alt-c

capitalize word from cursor

History#

Key

Action

Ctrl-p

previous command (same as Up arrow)

Ctrl-n

next command (same as Down arrow)

Ctrl-r

incremental reverse search

Ctrl-s

incremental forward search (requires stty -ixon)

Ctrl-g

abort current search, restore line

Alt-.

insert last argument of previous command

Alt-_

same as Alt-. (insert last argument)

!!

run previous command

!$

last argument of previous command

!^

first argument of previous command

!*

all arguments of previous command

!<n>

run history entry N

!<string>

run most recent command starting with <string>

^old^new

rerun previous command, substituting old for new

history -c

clear current-session history

history -d <n>

delete history entry N

Process Control#

Key

Action

Ctrl-c

send SIGINT to foreground process

Ctrl-z

suspend foreground process (SIGTSTP)

Ctrl-d

send EOF (close stdin); exit shell on empty line

Ctrl-l

clear screen (keeps current line)

Ctrl-s

pause terminal output (XOFF)

Ctrl-q

resume terminal output (XON)

Ctrl-\

send SIGQUIT to foreground process (core dump)

Completion#

Key

Action

Tab

complete filename / command / variable

Tab Tab

list all completions

Alt-?

list possible completions

Alt-*

insert all completions

Alt-/

filename completion (force)

Alt-~

username completion

Alt-$

variable completion

Alt-@

hostname completion

Alt-!

command-name completion

Misc#

Key

Action

Ctrl-x Ctrl-e

open current line in $EDITOR, run on save

Ctrl-x Ctrl-v

show readline / Bash version

Ctrl-_

undo last edit

Ctrl-/

undo (alternative binding)

Ctrl-j

newline (carriage return)

Ctrl-m

newline (same as Enter)

Ctrl-v <key>

insert next key literally (raw escape sequence)

Esc

prefix Alt- on terminals that won’t send Alt directly

set -o vi

switch to vi-style editing for this session

set -o emacs

switch back to emacs-style editing

bind -P

list every active readline binding

Strict Mode Header#

$ #!/usr/bin/env bash
$ set -euo pipefail
$ IFS=$'\n\t'

Cleanup with trap#

Always-on cleanup of a temp directory at script exit.

$ tmp=$(mktemp -d)
$ trap 'rm -rf "$tmp"' EXIT

Catch Ctrl-C (SIGINT) and report it.

$ trap 'echo interrupted' INT

Handle SIGTERM and exit non-zero so a supervisor sees the failure.

$ trap 'echo terminated; exit 1' TERM

Long Argument Parsing#

$ name=""
$ force=0
$ files=()

$ while [[ $# -gt 0 ]]; do
  $ case "$1" in
    $ -n|--name)   name="$2"; shift 2 ;;
    $ --name=*)    name="${1#*=}"; shift ;;
    $ -f|--force)  force=1; shift ;;
    $ -h|--help)   usage; exit 0 ;;
    $ --)          shift; files+=("$@"); break ;;
    $ -*)          echo "unknown: $1" >&2; exit 2 ;;
    $ *)           files+=("$1"); shift ;;
  $ esac
$ done

Default Variables#

$ port="${PORT:-8080}"
$ user="${USER:?USER required}"
$ logdir="${LOGDIR:=/var/log}"

Detect Script Directory#

$ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"

Logging#

$ log()  { printf '%s [%s] %s\n' "$(date -Iseconds)" "$1" "$2"; }
$ info() { log INFO  "$*"; }
$ warn() { log WARN  "$*" >&2; }
$ err()  { log ERROR "$*" >&2; }
$ die()  { err "$*"; exit 1; }

Safe Read of a File Line by Line#

$ while IFS= read -r line; do
  $ printf '%s\n' "$line"
$ done < input.txt

Iterate Files Safely#

$ shopt -s nullglob globstar
$ for f in **/*.go; do
  $ [[ -f $f ]] || continue
  $ printf '%s\n' "$f"
$ done

$ while IFS= read -r -d '' f; do
  $ process "$f"
$ done < <(find . -name '*.go' -print0)

Wait for a Service#

$ wait_for() {
  $ local host=$1 port=$2 timeout=${3:-30} elapsed=0
  $ until (echo > /dev/tcp/"$host"/"$port") 2>/dev/null; do
    $ (( elapsed >= timeout )) && return 1
    $ sleep 1
    $ (( elapsed++ ))
  $ done
$ }

$ wait_for db 5432 60 || die "db not reachable"

Confirm Prompts#

$ confirm() {
  $ local prompt=$1 reply
  $ read -r -p "$prompt [y/N] " reply
  $ [[ $reply =~ ^[Yy]$ ]]
$ }

$ confirm "Delete everything?" || exit 0

Lock File / Single-Instance#

$ lockfile=/tmp/myscript.lock
$ exec 200>"$lockfile"
$ flock -n 200 || { echo "already running"; exit 1; }
$ trap 'flock -u 200; rm -f "$lockfile"' EXIT

Retry With Backoff#

$ retry() {
  $ local max=$1 delay=$2 i=0
  $ shift 2
  $ until "$@"; do
    $ (( i++ ))
    $ (( i >= max )) && return 1
    $ sleep "$delay"
    $ delay=$(( delay * 2 ))
  $ done
$ }

$ retry 5 1 curl -fsSL https://api.example.com/health

Read JSON Field#

$ token=$(jq -r '.token' creds.json)

Parallel Map (xargs)#

$ printf '%s\n' "${urls[@]}" | xargs -P 8 -I{} curl -fsSL {}

Timestamp Filename#

$ ts=$(date -u +%Y%m%dT%H%M%SZ)
$ tar -czf "backup-${ts}.tgz" data/