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 |
|---|---|
|
move to start of line |
|
move to end of line |
|
move back one character |
|
move forward one character |
|
move back one word |
|
move forward one word |
|
jump between current position and start of line |
Editing#
Key |
Action |
|---|---|
|
delete character under cursor (EOF on empty line) |
|
delete character before cursor |
|
delete character before cursor (same as Backspace) |
|
delete word forward |
|
delete word backward (whitespace-delimited) |
|
delete word backward (non-alphanumeric-delimited) |
|
cut from cursor to end of line |
|
cut from cursor to start of line |
|
paste (yank) last cut text |
|
cycle through previous yanks after Ctrl-y |
|
transpose character with previous |
|
transpose word with previous |
|
uppercase word from cursor |
|
lowercase word from cursor |
|
capitalize word from cursor |
History#
Key |
Action |
|---|---|
|
previous command (same as Up arrow) |
|
next command (same as Down arrow) |
|
incremental reverse search |
|
incremental forward search (requires |
|
abort current search, restore line |
|
insert last argument of previous command |
|
same as Alt-. (insert last argument) |
|
run previous command |
|
last argument of previous command |
|
first argument of previous command |
|
all arguments of previous command |
|
run history entry N |
|
run most recent command starting with <string> |
|
rerun previous command, substituting old for new |
|
clear current-session history |
|
delete history entry N |
Process Control#
Key |
Action |
|---|---|
|
send SIGINT to foreground process |
|
suspend foreground process (SIGTSTP) |
|
send EOF (close stdin); exit shell on empty line |
|
clear screen (keeps current line) |
|
pause terminal output (XOFF) |
|
resume terminal output (XON) |
|
send SIGQUIT to foreground process (core dump) |
Completion#
Key |
Action |
|---|---|
|
complete filename / command / variable |
|
list all completions |
|
list possible completions |
|
insert all completions |
|
filename completion (force) |
|
username completion |
|
variable completion |
|
hostname completion |
|
command-name completion |
Misc#
Key |
Action |
|---|---|
|
open current line in |
|
show readline / Bash version |
|
undo last edit |
|
undo (alternative binding) |
|
newline (carriage return) |
|
newline (same as Enter) |
|
insert next key literally (raw escape sequence) |
|
prefix Alt- on terminals that won’t send Alt directly |
|
switch to vi-style editing for this session |
|
switch back to emacs-style editing |
|
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/