Skip to main content

Introduction to Bash

Learn Bash scripting step by step with real output: expansions, quoting, arrays, functions, arguments, strict mode, traps, ShellCheck and bats tests.

Bash is the shell you type commands into on most Linux servers, and the language of countless deployment scripts, CI steps, container entrypoints and cron jobs. It is easy to start with and full of sharp edges: unquoted variables that split into several arguments, set -e that does not stop where you expect, loops that lose their variables in a subshell.

This guide goes deep on Bash itself. Each part explains what Bash really does, shows it with real output, and points out the mistakes that cause bugs in production. Along the way you build logstat, a small command-line tool that summarizes web server logs: a function library in Part 6, a command you can run from anywhere in Part 7, options and validation in Part 8, and tests and CI in Part 10.

What You'll Learn

  1. What Is Bash? Versions, Startup Files and Your First Script: which Bash you have and why it matters, sh versus Bash, login and interactive shells, the practice files, and what is new in Bash 5.3.
  2. Bash Basics: How Bash Reads and Runs a Command Line: command types, exit codes, the order of expansions, quoting, pipelines, subshells and redirection.
  3. Working With Files in Bash: glob patterns and shopt options, file names with spaces and dashes, safe loops with find -print0, reading files and temporary files.
  4. Bash Variables: unset versus empty, parameter expansion, the Bash 5.2 & change, arithmetic traps, indexed and associative arrays, read.
  5. Bash if, case and Loops: [ ] versus [[ ]], regex matching with BASH_REMATCH, case, loops, and the subshell and stdin traps.
  6. Bash Functions: return values, namerefs, local and dynamic scope, libraries with source, and helpers for logging and retries.
  7. How to Make a Bash Script Executable and Run It: chmod +x, which permissions apply, shebangs, source versus running, PATH, cron and the errors you will meet.
  8. Bash Script Arguments: "$@", validation of numbers, ports and IP addresses, getopts, long options, standard input and exit codes.
  9. Bash Text Processing: printf, here documents, process substitution, awk, sed and jq, measured speed differences, parallel jobs and curl.
  10. Bash Best Practices: where set -euo pipefail fails, traps, flock, ShellCheck, shfmt, bats-core tests, a CI workflow and macOS portability.

Bash Best Practices at a Glance

  • Quote every expansion: "$var", "$(cmd)", "${array[@]}" (Part 2, Part 4).
  • Never loop over ls or $(cat file); use globs and while IFS= read -r (Part 3).
  • Use [[ ]] and (( )) in Bash scripts, and validate input before arithmetic (Part 5).
  • Declare variables local in functions, and return data by printing it (Part 6).
  • Give every script a Bash shebang and never run it with sh (Part 1, Part 7).
  • Build commands in arrays, never in strings for eval (Part 8).
  • Use printf, jq and one awk call instead of echo -e, grep on JSON and per-line loops (Part 9).
  • Use strict mode, one cleanup trap, ShellCheck and tests (Part 10).

Which Versions This Guide Uses

The guide was updated on 24 October 2026. The terminal output comes from real runs as a normal user (dev) on a machine called devbox:

  • Bash 5.2.21 on Ubuntu 24.04.5 LTS for almost everything; Debian 13 (5.2.37) behaves the same.
  • Bash 5.3.9 on Ubuntu 26.04.1 LTS for the Bash 5.3 features and messages. Upstream Bash is at 5.3 patch 20.
  • Bash 3.2.57, built from the GNU source, for what breaks on macOS, which still ships that version as /bin/bash.
  • ShellCheck 0.11.0, shfmt 3.14.1, bats-core 1.14.0 and actionlint 1.7.12, plus Ubuntu 24.04's own jq, curl, mawk and coreutils.

What is not a real run: the macOS commands in the tabs (BSD sed and date), the ssh, psql, cron and sudo lines, which need other machines or root, and the GitHub Actions workflow, which was checked with actionlint but not run on GitHub. Each of those is shown as code without output.

If you learned Bash from an older tutorial, these are the changes that matter most:

Old habit Now Why
`command` $(command) Nests without backslashes
[ "$a" = x -a "$b" = y ] [[ $a == x && $b == y ]] -a and -o are obsolete and ambiguous
$[ 1 + 2 ], expr 1 + 2 $(( 1 + 2 )) $[ ] is deprecated; expr starts a process
which cmd command -v cmd A builtin that knows functions and aliases
echo -e "a\tb" printf 'a\tb\n' echo differs between shells
eval "$cmd_string" "${cmd[@]}" with an array Safe with any argument
cat file | while read line while IFS= read -r line; do ...; done < file Keeps variables, spaces and backslashes
"${s/x/&}" for a literal & "${s/x/\&}", or shopt -u patsub_replacement & inserts the match since Bash 5.2
$RANDOM for anything that should be unpredictable $SRANDOM (Bash 5.1+), or better openssl rand for secrets $RANDOM is 15 bits from a simple generator

Prerequisites

  • A computer with Linux, macOS or Windows (WSL). Part 1 shows how to get a current Bash on each.
  • Basic terminal use. If cd, ls, cp and pipes are new to you, start with Linux command line basics in the Linux guide, whose shell scripting part is also a gentler first introduction to scripts.

Practice Hands-On

Part 1 creates ~/bash-practice, a small set of log, config and data files that every later part works with, so you can run each example yourself and get the same output. For quick experiments without installing anything, use the Linux Terminal simulator in your browser.

Keep Learning

Start with Part 1: What Is Bash?, or jump to making a script executable if you need that answer now.

Last updated:

Found an issue?