learninfra · Linux · Networking · Kubernetes · System Design · AI Infrastructure · Exam blueprints · Drills

Bash Scripting

Turn a release you type by hand into a script you can trust: arguments and options, conditions and loops, functions, exit codes, set -euo pipefail with traps, tracing, quoting and the portability traps.

An interactive Linux lesson: 21 steps, about 35 minutes, on a live simulation in your browser.

Every Thursday you release the shop by typing the same three commands from RUNBOOK.txt. Typed by hand, they are one typo away from an outage. The first step is to put them in a file.

The file's first line, #!/bin/bash, is the shebang: when you run the file, the kernel reads it and starts /bin/bash with the file's name as its argument. Without the execute bit, ./deploy.sh is refused with Permission denied (exit 126). After chmod +x it runs.

What you will learn

  1. A script is a program

    • From a runbook to a file: A script is a program like any other: the #! line names its interpreter, and it needs the execute bit to run as ./script.
    • ./deploy.sh, bash deploy.sh, source: ./script and bash script run in a child process; source runs the lines in your shell, so its cd and variables stay.
  2. Arguments and options

    • $1, $# and a usage message: A script's arguments are $1, $2 …; $# counts them. Check them first and fail with a usage line before doing anything.
    • "$@" keeps each argument whole: "$@" passes every argument on exactly as it arrived. Almost every other spelling re-splits or joins them.
    • Options with getopts: getopts reads options one letter at a time; a colon after a letter means it takes a value in $OPTARG. Then shift the options away.
  3. Conditions, loops, functions

    • Refuse bad input: [[ ]] and case: Validate every input at the top of a script with [[ ]] and case, and exit non-zero before anything is changed.
    • One line at a time: while read: while IFS= read -r line; do …; done < file reads a file one line at a time, exactly as written.
    • A counter that stays at zero: Each stage of a pipeline is a subshell. A loop at the end of a pipe cannot change your variables; use done < <(cmd).
    • Functions, local and return: A function is a small command inside the script: its arguments are $1…, local keeps its variables private, return sets its exit status.
    • Drill: a yes/no function
  4. When a command fails

    • Break it: a script that lies: By default bash runs every line no matter what failed, and a script's exit status is only its last command's.
    • set -euo pipefail and a trap: set -euo pipefail turns a failure into a stop. trap … EXIT runs cleanup however the script ends.
    • A failure set -e cannot see: local x=$(cmd), export x=$(cmd) and declare x=$(cmd) hide cmd's failure. Declare on one line, assign on the next.
    • Watch it run: bash -x: bash -x shows each command after expansion, just before it runs. It answers what really ran, with which values.
  5. Quoting and portability

    • Break it: a space in a file name: Quote every expansion, "$f" and "$@", unless you want it split. shellcheck catches the ones you miss.
    • Drill: make shellcheck happy
    • Break it: #!/bin/sh is not bash: The #! line picks the interpreter. #!/bin/sh is dash on Ubuntu: no [[ ]], no arrays, no source.
    • Break it: a script saved on Windows: A Windows file ends lines in \r\n. cat -A shows ^M$, file says CRLF; sed -i 's/\r$//' fixes it.
  6. Recap & playground

    • Three typed lines, one trusted tool: A script you can trust: strict mode, a usage line, validated inputs, a dry run, cleanup in a trap, every expansion quoted.
    • Cheat sheet
    • Playground