Advanced Bash-Scripting guide: a learning path for Bash 5

Updated November 2026

This guide is a map for learning Bash scripting today: five stages from first script to robust tools, a tested script skeleton to start from, a list of what changed since the Bash 2 era, and pointers into the official GNU Bash manual for each topic.

The address once held the Advanced Bash-Scripting Guide, a book-length tutorial written for Bash 2. That classic guide is kept by the LDP at tldp.org. This page is a new, shorter overview written for current Bash.

Check your own version with bash --version or echo "$BASH_VERSION". Bash patches are published on the GNU servers between releases and reach you through your distribution's updates; you do not need to build Bash yourself.

A learning path in five stages

  1. Commands, quoting and variables. Write and run a script, understand double versus single quotes, use "$var" everywhere and read arguments with "$@". Start with the Bash scripting introduction.
  2. Decisions and loops. Exit status, if with [[ ]] and (( )), case, for over globs, and while read -r over lines. Same page; practise by rewriting a task you do by hand every week.
  3. Functions and error handling. local variables, returning data by printing it, checking every command that matters, and the real behavior of set -euo pipefail. The introduction's "honest" section on set -e matters more than any other.
  4. Data and text. Indexed and associative arrays, mapfile, parameter expansion instead of sed/basename, process substitution and here-documents. See the advanced Bash scripting HOWTO.
  5. Tools other people use. getopts options and a usage message, traps for cleanup, debugging with set -x and PS4, ShellCheck in your editor. Same HOWTO. Make your own shell pleasant along the way with the Bash prompt HOWTO.

At each stage, run ShellCheck on what you write and read the explanation behind every warning. That habit teaches the quoting rules faster than any book.

A script skeleton

This template collects the patterns from the HOWTOs: a usage message, getopts, error helpers, a temporary file removed by an EXIT trap, and all logic inside a main function. It passes ShellCheck and runs unchanged in Bash 5.2 and 5.3.

#!/usr/bin/env bash
# name.sh - one line saying what this script does
# Usage: name.sh [-n] [-v] FILE...
set -uo pipefail

readonly PROG=${0##*/}
dry_run=0
verbose=0
tmp=            # global, so the EXIT trap can still see it

die()  { printf '%s: %s\n' "$PROG" "$*" >&2; exit 1; }
log()  { (( verbose )) && printf '%s: %s\n' "$PROG" "$*" >&2; return 0; }
usage() { printf 'Usage: %s [-n] [-v] FILE...\n' "$PROG"; }

main() {
    local opt
    while getopts ':nvh' opt; do
        case $opt in
            n)  dry_run=1 ;;
            v)  verbose=1 ;;
            h)  usage; exit 0 ;;
            *)  usage >&2; exit 2 ;;
        esac
    done
    shift $(( OPTIND - 1 ))
    (( $# > 0 )) || { usage >&2; exit 2; }

    tmp=$(mktemp) || die "cannot create a temporary file"
    trap 'rm -f -- "$tmp"' EXIT

    local f
    for f in "$@"; do
        [[ -r $f ]] || die "cannot read $f"
        log "processing $f"
        if (( dry_run )); then
            printf 'would process %s\n' "$f"
        else
            sort -u -- "$f" > "$tmp" && wc -l < "$tmp"
        fi
    done
}

main "$@"

Try it with ./name.sh -v /etc/hostname /etc/shells (prints the number of unique lines in each file) and ./name.sh -n /etc/hostname (dry run). Notes on the choices:

What changed since Bash 2

Old tutorials, including the classic one, were written when Bash 2 was current. These features arrived since then (from the Bash NEWS file); prefer them over the old workarounds.

Selected Bash features by release
ReleaseFeatureReplaces
4.0 (2009)associative arrays (declare -A), mapfile/readarray, ${var^^} and ${var,,} case changes, globstar (**), coproc, |&parallel arrays, while read loops into arrays, tr, find for simple recursion
4.3namerefs (declare -n)eval to reach variables by name
4.4${var@Q} and the other @ transformations, PS0, inherit_errexit, mapfile -dhand-written quoting code
5.0EPOCHSECONDS, EPOCHREALTIME, BASH_ARGV0$(date +%s)
5.1SRANDOM (32-bit random numbers), PROMPT_COMMAND as an array, ${var@U}/@u/@L, wait -p$RANDOM for anything that matters, long PROMPT_COMMAND strings
5.2patsub_replacement (& in ${var/pat/rep} means the match), @k(watch out: changes old scripts that used a literal &)
5.3 (2025)${ command; } and ${|command;} substitution without a subshell, GLOBSORT, BASH_MONOSECONDS, BASH_TRAPSIG, source -p PATH$(…) where a fork is too slow, ls -t for sorting by time

Things to stop doing: backticks (use $(…)), [ ] with unquoted variables (use [[ ]] in Bash, or quote everything), expr and let (use $(( ))), function name () mixtures (use name() { …; }), and parsing ls.

Bash or POSIX sh?

A script that starts with #!/bin/sh must use only POSIX shell features, because /bin/sh is dash on Debian and Ubuntu and Bash in POSIX mode on Fedora and the RHEL family. Write #!/usr/bin/env bash (or #!/bin/bash) whenever you use arrays, [[ ]], local with options, or any feature in the table above. To check a script meant for sh, run shellcheck -s sh script; it flags every Bash-only construct.

Bash is a poor fit once a script needs complex data structures, floating-point math, or more than a few hundred lines of logic. At that point a general-purpose language such as Python is easier to test and maintain, and can still call the same commands.

Where the Bash manual covers it

The Bash Reference Manual (edition 5.3) is the definitive reference; man bash and info bash give you the same material offline, and help BUILTIN (for example help getopts) prints a quick summary of any builtin.

Topics and Bash manual sections
TopicManual section
Quoting3.1.2 Quoting
if, case, [[ ]], loops3.2.5 Compound Commands; 6.4 Bash Conditional Expressions
Functions3.3 Shell Functions
Parameter expansion3.5.3 Shell Parameter Expansion
Command and process substitution3.5.4 Command Substitution; 3.5.6 Process Substitution
Redirections, here-documents3.6 Redirections (3.6.6 Here Documents, 3.6.7 Here Strings)
Exit status and signals3.7.5 Exit Status; 3.7.6 Signals
getopts, trap, read, mapfile4.1 Bourne Shell Builtins; 4.2 Bash Builtin Commands
set -e, pipefail, shopt options4.3.1 The Set Builtin; 4.3.2 The Shopt Builtin
Special variables5.2 Bash Variables
Startup files (~/.bashrc, ~/.bash_profile)6.2 Bash Startup Files
Arithmetic and arrays6.5 Shell Arithmetic; 6.7 Arrays
Prompts6.9 Controlling the Prompt
POSIX mode and compatibility levels6.11 Bash and POSIX; 6.12 Shell Compatibility Mode
Line editing and history8 Command Line Editing; 9 Using History Interactively

Getting help

Sources