# Linux Terminal 04 — Reliable Bash Scripts and Backups You Can Restore

**Track:** IT Foundations → Linux operations  
**Prerequisites:** Linux Terminal 01–03: paths, quoting basics, pipes, exit statuses, and evidence-led troubleshooting  
**Format:** 40-minute lesson + 25-minute local lab  
**Outcome:** validate script inputs, handle expected failures, create a local archive, verify its digest, and prove that its files restore correctly.

## 1. The backup command said “completed”

In Terminal 02, you learned that a script is a sequence of commands. Now imagine saving your learning notes every evening. Your script prints “Backup completed,” so you assume the notes are recoverable. A week later you need one file. The archive is missing a directory, or the copied files are from an earlier run.

The weakness was not necessarily the copy command. It was the promise attached to the output. A script should report success only after the checks that success actually requires.

Our running scenario is a small study folder containing ordinary notes, a filename with a space, and a hidden lesson marker. We will archive the folder, calculate an integrity digest, restore into a separate directory, and compare content. Then we will deliberately change a restored copy and prove the comparison detects it.

This is a local teaching utility for trusted, non-changing files. It is not a complete backup system for a running database, an entire operating system, or hostile directories. The boundary lets us learn the mechanics precisely before adding operational complexity.

## 2. Design the script’s contract first

A useful contract answers four questions: what does the script accept, what does it create, what counts as failure, and what does it promise after success?

The supplied `safe-backup.sh` accepts two arguments: an existing source directory and a **new** backup directory whose parent already exists. It creates an archive, a checksum file, and a completion marker. It rejects a missing source, an existing destination, and a destination nested inside the source.

Why reject nesting? A backup stored inside its own source may be included in later archives and grow unexpectedly. Why reject an existing destination? A teaching script should not silently replace the only previous copy. A new directory per run makes each result easy to inspect.

The script reports `CREATED`, followed by a reminder that restore verification is separate. The marker says that the archive and digest were created. It does not claim a successful restore or protection against disk failure. That language is part of the interface.

## 3. Quote values that represent one argument

Consider a source directory named `study notes`. Unquoted expansion can turn one path into multiple words. Double quotes preserve it as one argument while still expanding the variable:

```bash
printf '%s\n' "$cct_source"
tar -cf "$cct_archive" -C "$cct_source" .
```

Single quotes are useful for literal text, such as a format string. Double quotes are useful when a value should expand but remain together. Prefer `printf '%s\n' "$value"` over treating arbitrary text as a format string. [Bash quoting reference](https://www.gnu.org/software/bash/manual/html_node/Quoting.html)

This lesson uses a `cct_` prefix for task variables so they do not overwrite common shell or system variables. The source is resolved to a physical absolute path. The destination’s parent is resolved separately before its new name is appended.

Do not parse `ls` output to enumerate backup inputs. Filenames can contain spaces and other characters that make line-oriented parsing unreliable. Archiving `.` from a specific source directory includes the hidden files too, without asking the shell to expand a `*` wildcard.

## 4. Exit status is part of the result

A zero exit status usually means a command completed successfully; a nonzero status signals a different outcome. The meaning depends on the command. For example, `cmp` distinguishes equal files, different files, and errors. A “files differ” result is exactly what an intentional negative test should produce.

Handle an expected failure explicitly:

```bash
if cmp -s original.txt restored.txt; then
  printf 'Contents match\n'
else
  cct_status=$?
  printf 'Comparison returned %s\n' "$cct_status"
fi
```

Capture `$?` immediately. The next command changes it. If a command is prefixed with `!`, the status seen by the surrounding condition is inverted; do not invert it and then assume `$?` still holds the original failure code.

Error output belongs on standard error. Progress and intended results can use standard output. The distinction lets a caller save a useful result while still seeing a failure. A clear message such as “source must exist” is more actionable than a final “done” after an earlier error scrolled away.

## 5. Shell options help, but checks still matter

`set -u` treats many unset-variable expansions as errors. `set -o pipefail` makes a pipeline’s result reflect a failing component instead of only its final command. They reduce some surprises, but they do not validate the meaning of your inputs.

You will often see `set -e`, which exits on many unhandled failures. It has exceptions, including conditions and parts of command lists. A function called in a tested context can also behave differently than a beginner expects. Learn those semantics before relying on it as a complete error policy. [Bash set options](https://www.gnu.org/s/bash/manual/html_node/The-Set-Builtin.html), [pipeline status rules](https://www.gnu.org/software/bash/manual/html_node/Pipelines.html)

Our backup utility uses explicit checks for critical steps. If archive creation, listing, finalization, or hashing fails, it reports the failure and exits. The runner uses stricter options while putting intentional negative tests inside `if` statements. Read both files and notice where the responsibilities differ.

A syntax check with `bash -n` catches parsing errors before execution. It does not prove filenames, permissions, disk capacity, or restore behavior are correct. Those need meaningful tests.

## 6. Temporary data needs an owner and a boundary

The lab creates a fresh private directory with `mktemp -d`, then puts its fixtures inside. A fixed location such as `/tmp/my-backup` can collide with another run or existing files. A generated name avoids that simple collision. [GNU mktemp reference](https://www.gnu.org/s/coreutils/manual/html_node/mktemp-invocation.html)

The script also sets `umask 077`, reducing access granted to newly created files and directories. That does not encrypt the archive. It does not change permissions on unrelated existing files. It is one useful default for newly created local artifacts.

Cleanup is installed through an `EXIT` trap. Interrupt and terminate handlers exit with appropriate statuses, allowing the exit cleanup to run. No trap can clean up after SIGKILL or a machine power loss, so temporary artifacts can remain after those events. [Bash trap reference](https://www.gnu.org/s/bash/manual/html_node/Bourne-Shell-Builtins.html)

Our cleanup lists only exact fixture files, then removes empty directories. It uses no recursive deletion. If something unexpected appears, the last directory removal fails and leaves the material for inspection. That behavior favors an explainable leftover over a broad cleanup command.

## 7. Create the archive in stages

The utility uses a new destination directory as a container for one backup. It creates `archive.tar.partial`, checks that the archive can be listed, then renames it to `archive.tar`.

The central command is:

```bash
tar -cf "$cct_backup/archive.tar.partial" -C "$cct_source" .
```

`-c` creates, `-f` names the archive, and `-C` changes the input directory for archiving. The final `.` selects that directory’s content, including dotfiles. The archive stores relative member names so the later restore can target a separate location. [GNU tar manual](https://www.gnu.org/software/tar/manual/tar.html)

Listing with `tar -tf` checks that tar can read the archive structure. It is a useful early check, but it does not prove every restored file matches the source. A readable table of contents and a successful restore are different evidence.

The utility keeps incomplete output on a failed creation for inspection. It writes its completion marker only after archive finalization and digest creation. Multiple files and a marker do not form a production transaction; a power failure or disk problem still requires careful recovery procedures.

## 8. An integrity digest has a specific job

SHA-256 produces a digest derived from the archive’s bytes. Save it after creating the archive, then check it when reading or transferring the archive later.

On a common Linux installation:

```bash
sha256sum archive.tar > SHA256SUMS
sha256sum -c SHA256SUMS
```

On macOS, the usual equivalent is:

```bash
shasum -a 256 archive.tar > SHA256SUMS
shasum -a 256 -c SHA256SUMS
```

The supplied utility selects whichever supported tool is available. [GNU checksum documentation](https://www.gnu.org/software/coreutils/manual/coreutils.html), [Perl shasum reference](https://perldoc.perl.org/5.32.0/shasum)

A matching digest means the archive matches the digest you are checking. It does not prove the original source was complete, that the application can use the files, or that a malicious actor did not replace both archive and checksum. Protect and store verification information appropriately in real systems. For our local exercise, the digest is one integrity check between creation and restore.

## 9. Prove the restore

Restore into an empty directory you created for the test:

```bash
tar -xf "$cct_backup/archive.tar" -C "$cct_restore"
```

Here `-x` extracts. This exercise extracts only the archive the runner just created from its own fixtures. Do not use arbitrary downloaded archives as interchangeable lab inputs.

Next, compare the original and restored regular files with `cmp`. Silence with exit status zero means the compared contents match. A recursive `diff` provides an additional directory-level comparison for this small fixture. Neither is a complete test of ownership, access controls, extended attributes, every symlink behavior, or application consistency. Those require an expanded design.

Our test includes `study plan.txt` to exercise spaces and `.lesson` to exercise hidden names. These are meaningful boundary cases because weak shell scripts often mishandle them. After the good restore passes, the runner modifies only the restored notes and expects `cmp` to report a difference.

That negative test proves your verification can fail when content actually differs. A test that always prints “PASS” is only decoration.

## 10. Run the supplied lab

Requirements: Bash 3.2+, tar, common file utilities, and either `sha256sum` or `shasum`. No internet, cloud account, scheduled task, system configuration change, or administrator privileges are needed.

From this lesson’s directory:

```bash
bash -n lab/safe-backup.sh
bash -n lab/run-lab.sh
bash lab/run-lab.sh
```

The runner creates fixtures, invokes the utility, checks the digest, restores, compares, and tests four failure boundaries. Expected stable output includes:

```text
archive.tar: OK
PASS: archive digest matches and all three files restore, including a space and a hidden name.
PASS: missing source rejected before output creation.
PASS: existing backup refused; no overwrite.
PASS: destination inside source rejected.
PASS: modified restored copy detected as different.
```

The temporary path varies. At exit the runner removes only the files it created and their empty directories. If you interrupt a run, the same cleanup is attempted. The standalone utility intentionally retains created backups; the lab runner removes its own demonstration copy after testing.

If a prerequisite is missing, the script reports it. Review the indicated tool’s official installation instructions separately. The exercise itself installs nothing.

## 11. Know what must change for real operations

A local archive on the same disk does not survive that disk’s failure. Repeated local copies do not automatically provide retention, encryption, off-site recovery, access control, monitoring, or reliable scheduling. A live database also needs its own consistency mechanism; archiving changing files may capture an unusable mixture of states.

The next design questions are therefore operational. How much work can be lost? How quickly must service return? Where is another protected copy stored? Who can restore it? How often do you rehearse restoration? These decisions define the backup requirement before you choose more commands.

For a portfolio entry, include the script contract, actual lab output, and an explanation of one detected failure. State that the fixture restored correctly on your tested platform. Do not claim a production disaster-recovery capability from a three-file demonstration.

**Sources checked:** September 18, 2026. The scripts target Bash 3.2+ with common Linux/macOS tools, avoid GNU-only tar verification flags, and label checksum command differences. The tested fixture covers regular text files, a hidden name, and a space; broader filesystem metadata and live applications are outside scope.
