← Back to Linux

Shell scripting

Shell scripts automate tasks by saving commands in files, adding variables, conditions, loops, and functions. They are the everyday glue of Linux administration.

Writing a script

A script is a text file of commands. Its first line, the shebang, names the interpreter: #!/usr/bin/env bash or #!/bin/bash. Make it executable with chmod +x backup.sh and run it with ./backup.sh. Comments start with #. Scripts run in a new shell process, so variables they set do not affect the calling shell unless the script is sourced with source or a dot.

Arguments and exit codes

Scripts receive arguments as $1, $2, and so on; $0 is the script name, $# the number of arguments, and "$@" all arguments, each properly quoted. Every command returns an exit status: 0 for success and non-zero for failure, available in $?. Scripts should exit with meaningful codes using exit 0 or exit 1, so other programs can tell whether they worked.

Conditions

if tests a command's exit status: if grep -q root /etc/passwd; then echo found; fi. The [[ ]] construct in Bash tests conditions: [[ -f file ]] (regular file exists), [[ -d dir ]] (directory exists), [[ -z $s ]] (empty string), [[ $a == $b ]] (string equality), and numeric comparisons with -eq, -lt, and -gt, or (( count > 5 )). && and || run a command only if the previous one succeeded or failed.

Loops

for loops iterate over words or files: for f in *.log; do gzip "$f"; done. C-style loops count: for ((i = 1; i <= 5; i++)). while loops repeat while a condition holds, and while read -r line; do ...; done < file processes a file line by line. break and continue control loops. case statements match a value against patterns, which is handy for command-line options.

Functions and structure

Functions group commands: backup() { tar -czf "$1.tar.gz" "$1"; } is called as backup photos. local declares variables limited to the function. Well-structured scripts start with settings, define functions, validate arguments, and then call a main function, keeping behaviour easy to read and test.

Safer scripts

set -e exits when a command fails; set -u treats unset variables as errors; set -o pipefail makes a pipeline fail if any stage fails. Together, set -euo pipefail catches many silent failures, though it has subtleties. Always quote variables, check that inputs exist, use mktemp for temporary files, and clean up with trap 'rm -f "$tmp"' EXIT. The ShellCheck tool finds common mistakes automatically.

When to use another language

Shell excels at running and connecting programs, file operations, and quick automation. For complex logic, data structures, error handling, or anything longer than a page or two, languages such as Python are usually clearer and safer. Many teams use shell for thin wrappers and Python for heavier tooling.

Worked example

A script that backs up a directory: #!/usr/bin/env bash; set -euo pipefail; src=${1:?usage: backup.sh DIR}; [[ -d $src ]] || { echo "not a directory: $src" >&2; exit 1; }; dest="/backups/$(basename "$src")-$(date +%F).tar.gz"; tar -czf "$dest" "$src"; echo "saved $dest". Run as ./backup.sh ~/photos on 25 October 2026, it creates /backups/photos-2026-10-25.tar.gz, or exits with an error if the argument is missing or not a directory.

Key takeaways

Scripts start with a shebang such as #!/usr/bin/env bash, are made executable with chmod +x, and run in their own process unless sourced. $1 to $9, $#, and "$@" access arguments; exit status 0 means success, checked via $?. if tests commands; [[ ]] tests files, strings, and numbers; && and || chain on success or failure. for, while read, break, continue, and case control flow. Functions with local variables structure scripts. set -euo pipefail, quoting, mktemp, trap, and ShellCheck make scripts safer. Use Python or similar for complex logic.

Flashcard

Shebang

First line naming the interpreter

#!/usr/bin/env bash.

Check your understanding

A script runs cd /missing_dir followed by rm -rf ./*. Why is set -e an important safeguard here?

← Previous lesson Next lesson →