Computing FoundationsInside the Machine › Day 5

Hands-on lab — Day 5: Text, Images, and Sound as Data

Commands

Setup

cd labs/sections/computing-foundations/day-005-text-images-and-sound-as-data

Run

bash examples/inspect_bytes.sh
bash starter/inspect_bytes.sh

Test

bash tests/run_tests.sh

File tree

examples/inspect_bytes.sh
examples/make_samples.sh
examples/samples/hello.txt
examples/samples/tiny.png
examples/samples/unicode.txt
expected-output/inspect-bytes-macos.txt
expected-output/NOTES.md
expected-output/test-run-macos.txt
metadata.yml
README.md
requirements/README.md
security.md
starter/byte-detective-worksheet.md
starter/inspect_bytes.sh
tests/run_tests.sh
troubleshooting.md

Lab README

Day 005 lab — X-Ray Your Files: Seeing Bytes with hexdump

Lesson

Purpose

Day 5's lesson claims that text, images, and sound are nothing but bytes under agreed conventions. This lab lets you verify that claim with your own eyes: you point xxd and file at three small committed sample files — pure ASCII text, UTF-8 text with accents and an emoji, and a real 145-byte PNG image — and read the actual bytes: ASCII codes, multi-byte UTF-8 sequences, and the PNG magic bytes 89 50 4E 47.

Learning objectives

  • Read a hex dump: offsets on the left, bytes in the middle, the ASCII interpretation on the right.
  • Locate 1-, 2-, 3-, and 4-byte UTF-8 sequences in a real file and explain why byte count and character count differ.
  • Identify a file format from its magic bytes and verify it with file.
  • Demonstrate that a file's extension is a naming convention while its content determines its true type.
  • Complete a small shell script by filling in four well-specified exercises, and answer a worksheet from observed bytes only.

Prerequisites

  • The Day 5 lesson (read it first — it explains every byte you will see).
  • Comfort running commands in a terminal (Day 1 lab).
  • No programming experience required; every command is given and explained.

Supported operating systems

  • macOS — fully supported (tested on macOS with Apple Silicon).
  • Linux — fully supported (any distribution with xxd and file; install xxd from your package manager if a minimal image lacks it).
  • Windows — run the scripts unmodified inside WSL; PowerShell users can explore manually with Format-Hex, but the tests assume a Unix-style shell.

Hardware requirements

Any computer. The samples total 191 bytes; the lab only reads them and writes to a throwaway temp directory.

Required software

  • bash (3.2 or newer — preinstalled on macOS and Linux).
  • xxd (preinstalled on macOS; ships with vim on Linux) and file (preinstalled everywhere). hexdump -C works as a stand-in for xxd.

Free and open-source options

Everything in this lab is free: bash, xxd, hexdump, and file are open-source or ship with your OS. No account, API key, or download is needed — the sample files are committed to the repository, and examples/make_samples.sh can rebuild them offline at any time.

Installation

None. From the repository root:

cd labs/sections/computing-foundations/day-005-text-images-and-sound-as-data

File structure

day-005-text-images-and-sound-as-data/
├── README.md                        ← you are here
├── metadata.yml                     ← machine-readable lab metadata
├── starter/
│   ├── inspect_bytes.sh             ← YOUR working file (4 exercises)
│   └── byte-detective-worksheet.md  ← 10 questions answered from real bytes
├── examples/
│   ├── inspect_bytes.sh             ← completed reference walkthrough
│   ├── make_samples.sh              ← rebuilds the samples byte by byte
│   └── samples/
│       ├── hello.txt                ← 14 bytes of pure ASCII
│       ├── unicode.txt              ← UTF-8: é (2 bytes), € (3), emoji (4)
│       └── tiny.png                 ← real 145-byte PNG, 2×2 pixels
├── tests/
│   └── run_tests.sh                 ← automated checks
├── expected-output/
│   ├── inspect-bytes-macos.txt      ← real captured walkthrough run
│   ├── test-run-macos.txt           ← real captured test run
│   └── NOTES.md                     ← what must match vs. may differ
├── requirements/
│   └── README.md                    ← dependency statement
├── troubleshooting.md
└── security.md

How to run

From this directory:

## 1. Watch the finished walkthrough first
bash examples/inspect_bytes.sh

## 2. Your task: complete the four exercises in the starter, then run it
bash starter/inspect_bytes.sh

## 3. Fill in starter/byte-detective-worksheet.md from what you observed

## 4. Check your work
bash tests/run_tests.sh

What the commands do

  • bash examples/inspect_bytes.sh — for each sample file, runs file (identify by content), wc -c/wc -m (bytes vs. characters), and xxd (hex dump), with printed notes pointing at the interesting bytes: ASCII codes in hello.txt, the sequences c3 a9, e2 82 ac, and f0 9f 8e 89 in unicode.txt, and the PNG signature plus readable chunk names in tiny.png. Section 4 copies the samples under swapped extensions into a temp directory and shows file judging content, not names.
  • bash starter/inspect_bytes.sh — the same investigation as a skeleton: four placeholder lines, each preceded by a comment naming the exact command to put there (xxd, xxd -l 8, and a cp … && file … line).
  • bash examples/make_samples.sh — optional; rebuilds the three samples deterministically (printf byte escapes for the text files, base64 decoding for the PNG) and prints file's verdict on each.
  • bash tests/run_tests.sh — checks that the samples exist and are identified correctly by file, that xxd shows the exact expected byte sequences (PNG signature 89504e470d0a1a0a, the UTF-8 sequences, the exact 14 bytes of hello.txt), that content beats extension, and that both scripts run cleanly.

Expected output

See expected-output/inspect-bytes-macos.txt for the full real captured run. The heart of it:

00000000: 4865 6c6c 6f2c 2077 6f72 6c64 210a       Hello, world!.

00000000: 6361 66c3 a920 636f 7374 7320 3320 e282  caf.. costs 3 ..
00000010: ac0a 72c3 a973 756d c3a9 0af0 9f8e 890a  ..r..sum........

00000000: 8950 4e47 0d0a 1a0a                      .PNG....

Your output must show the same bytes — bytes do not vary by machine. Only file's phrasing and locale-dependent character counts may differ slightly (see expected-output/NOTES.md).

Validation steps

  1. Run bash starter/inspect_bytes.sh — it must exit without errors and no longer print any not completed yet line.
  2. In your own output, point at: the byte 48 (H), the pair c3 a9 (é), and the four bytes 89 50 4e 47 (PNG signature).
  3. Confirm the worksheet has all 10 answers filled in, each backed by a command you actually ran.
  4. Run the tests (next section) — all checks must pass.

Tests

bash tests/run_tests.sh

Expected final line: 18 checks, 0 failure(s). while the starter still has placeholders (structure-only check), rising to 20 checks, 0 failure(s). once you have completed all four exercises. The command exits 0 on success and non-zero on any failure, so it can run in CI.

Cleanup

Nothing to clean up: the scripts write only inside mktemp -d scratch directories, which they remove themselves. To reset your work: git checkout -- starter/inspect_bytes.sh starter/byte-detective-worksheet.md.

Troubleshooting

See troubleshooting.md for the full list (missing xxd, locale-dependent wc -m, emoji rendering, file wording differences, corrupted PNG after line-ending translation, BOM bytes).

Security notes

See security.md. Short version: read-only inspection, no network, no privileges — and the rename experiment is itself a security lesson: extensions are claims, magic bytes are evidence.

Extension exercises

  1. Hex-dump other real files on your machine with xxd file | head — a JPEG photo (starts ff d8 ff), a PDF (25 50 44 46, which spells %PDF), a ZIP archive (50 4b, "PK"). Keep a small table of the magic bytes you find.
  2. Use xxd -b examples/samples/hello.txt | head -3 to see the same bytes as raw binary, and check that H is 01001000 (72).
  3. Save the same short sentence from your editor as UTF-8 and as UTF-16, dump both, and explain the size difference and the ff fe byte-order mark you will find.
  4. Run strings examples/samples/tiny.png and compare with the full hex dump: which parts of a binary file does strings surface, and why is it a favorite first tool for inspecting unknown binaries?
  • Previous day: Day 4 — Binary and Data Representation: Bits, Bytes, and Numbers (../day-004-binary-and-data-representation-bits-bytes/)
  • Next day: Day 6 — Operating Systems: What They Do and Why (../day-006-operating-systems-what-they-do-and/)

Expected output

NOTES.md

# Expected output — what must match and what may differ

Both files in this directory were captured from real runs on macOS
(Apple Silicon).

## Files

- `inspect-bytes-macos.txt` — full output of `bash examples/inspect_bytes.sh`.
- `test-run-macos.txt` — full output of `bash tests/run_tests.sh`
  (final line: `18 checks, 0 failure(s).` with the starter untouched).

## Must be identical on every platform

The **bytes** never change — they are the whole point of the lab:

- `hello.txt` dump: `4865 6c6c 6f2c 2077 6f72 6c64 210a` (14 bytes).
- `unicode.txt` dump: contains `c3 a9` (é), `e2 82 ac` (€), and
  `f0 9f 8e 89` (party-popper emoji); 32 bytes, 24 characters.
- `tiny.png` first 8 bytes: `8950 4e47 0d0a 1a0a`; 145 bytes total.

## May differ slightly by platform

- The wording of `file` output: e.g. Linux may print `Unicode text, UTF-8
  text` as just `UTF-8 text`, or append `, with no line terminators` for
  some files. The tests only require the substrings `ASCII text`, `UTF-8`,
  and `PNG image data`.
- `wc -m` requires a UTF-8 locale to count 24 characters; in a `C`/POSIX
  locale it counts bytes (32). See `troubleshooting.md`.
- Temporary-directory paths in section 4 of the walkthrough are hidden
  because the script prints bare file names.
- Once you complete all four starter exercises, the test count rises from
  18 to 20 checks (the strict checks replace the structure-only check).

inspect-bytes-macos.txt


=== 1. hello.txt — plain ASCII, one byte per character ===
hello.txt: ASCII text
size in bytes:      14
size in characters: 14
00000000: 4865 6c6c 6f2c 2077 6f72 6c64 210a       Hello, world!.
Note: 13 visible characters + 1 newline (0a) = 14 bytes.
Note: 'H' = 48 hex = 72 decimal, exactly its ASCII code.

=== 2. unicode.txt — UTF-8 with 1-, 2-, 3-, and 4-byte characters ===
unicode.txt: Unicode text, UTF-8 text
size in bytes:      32
size in characters: 24
00000000: 6361 66c3 a920 636f 7374 7320 3320 e282  caf.. costs 3 ..
00000010: ac0a 72c3 a973 756d c3a9 0af0 9f8e 890a  ..r..sum........
Note: bytes and characters differ because some characters need
several bytes. Look for these multi-byte UTF-8 sequences above:
  c3 a9        = e-acute      (U+00E9, 2 bytes)
  e2 82 ac     = euro sign    (U+20AC, 3 bytes)
  f0 9f 8e 89  = party emoji  (U+1F389, 4 bytes)
xxd prints a dot in the right-hand column for every byte that is
not printable ASCII — that is why the accents look like dots.

=== 3. tiny.png — a real binary file with magic bytes ===
tiny.png: PNG image data, 2 x 2, 8-bit/color RGB, non-interlaced
size in bytes:      145
first 8 bytes (the PNG signature):
00000000: 8950 4e47 0d0a 1a0a                      .PNG....
89 50 4e 47 0d 0a 1a 0a — bytes 2-4 spell 'PNG' in ASCII.
full dump (note the readable chunk names IHDR, tEXt, IDAT, IEND):
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 0002 0000 0002 0802 0000 00fd d49a  ................
00000020: 7300 0000 3a74 4558 7443 6f6d 6d65 6e74  s...:tEXtComment
00000030: 0044 6179 2035 2073 616d 706c 653a 2032  .Day 5 sample: 2
00000040: 7832 2070 6978 656c 7320 2d20 7265 642c  x2 pixels - red,
00000050: 2067 7265 656e 2c20 626c 7565 2c20 7768   green, blue, wh
00000060: 6974 6594 647d 7b00 0000 1249 4441 5478  ite.d}{....IDATx
00000070: da63 f8cf c0c0 00c2 0cff 8100 001f ee05  .c..............
00000080: fbf1 abba 7700 0000 0049 454e 44ae 4260  ....w....IEND.B`
00000090: 82                                       .

=== 4. Extensions lie; magic bytes do not ===
Renamed hello.txt -> hello.png. What does 'file' say?
hello.png: ASCII text
Renamed tiny.png -> tiny.txt. What does 'file' say?
tiny.txt: PNG image data, 2 x 2, 8-bit/color RGB, non-interlaced
'file' reads the CONTENT (magic bytes), not the name: the fake
.png is still ASCII text, and the fake .txt is still PNG image data.

=== End of byte walkthrough ===

test-run-macos.txt

Checking sample files exist ...
  ok: samples/hello.txt exists and is non-empty
  ok: samples/unicode.txt exists and is non-empty
  ok: samples/tiny.png exists and is non-empty
Checking 'file' identifies each sample by content ...
  ok: hello.txt is ASCII text
  ok: unicode.txt is UTF-8 text
  ok: tiny.png is PNG image data
Checking the exact bytes with xxd ...
  ok: tiny.png starts with the PNG magic bytes 89 50 4e 47 0d 0a 1a 0a
  ok: unicode.txt contains the 2-byte sequence c3 a9 (e-acute)
  ok: unicode.txt contains the 3-byte sequence e2 82 ac (euro sign)
  ok: unicode.txt contains the 4-byte sequence f0 9f 8e 89 (emoji)
  ok: hello.txt is exactly the 14 ASCII bytes of 'Hello, world!' + newline
Checking content beats extension ...
  ok: 'file' still sees PNG content behind a .txt name
Running the reference walkthrough (examples/inspect_bytes.sh) ...
  ok: reference walkthrough exits successfully
  ok: walkthrough output shows the PNG magic bytes
  ok: walkthrough output shows the e-acute bytes
  ok: walkthrough shows 'file' identifying the PNG
Running your starter script (starter/inspect_bytes.sh) ...
  ok: starter script exits successfully
Note: starter still has unfinished exercises — structure checked only.
  ok: starter prints its header

18 checks, 0 failure(s).

Source files

examples/inspect_bytes.sh (2362 bytes)
#!/usr/bin/env bash
# Day 005 lab — completed reference walkthrough: X-ray the three sample
# files with `file`, `wc`, and `xxd` and point at what the bytes mean.
#
# Run from anywhere:  bash examples/inspect_bytes.sh
set -euo pipefail

samples="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/samples"
cd "${samples}"   # so every tool prints short, machine-neutral file names

section() {
  echo
  echo "=== $1 ==="
}

section "1. hello.txt — plain ASCII, one byte per character"
file hello.txt
echo "size in bytes:      $(wc -c < hello.txt | tr -d ' ')"
echo "size in characters: $(wc -m < hello.txt | tr -d ' ')"
xxd hello.txt
echo "Note: 13 visible characters + 1 newline (0a) = 14 bytes."
echo "Note: 'H' = 48 hex = 72 decimal, exactly its ASCII code."

section "2. unicode.txt — UTF-8 with 1-, 2-, 3-, and 4-byte characters"
file unicode.txt
echo "size in bytes:      $(wc -c < unicode.txt | tr -d ' ')"
echo "size in characters: $(wc -m < unicode.txt | tr -d ' ')"
xxd unicode.txt
echo "Note: bytes and characters differ because some characters need"
echo "several bytes. Look for these multi-byte UTF-8 sequences above:"
echo "  c3 a9        = e-acute      (U+00E9, 2 bytes)"
echo "  e2 82 ac     = euro sign    (U+20AC, 3 bytes)"
echo "  f0 9f 8e 89  = party emoji  (U+1F389, 4 bytes)"
echo "xxd prints a dot in the right-hand column for every byte that is"
echo "not printable ASCII — that is why the accents look like dots."

section "3. tiny.png — a real binary file with magic bytes"
file tiny.png
echo "size in bytes:      $(wc -c < tiny.png | tr -d ' ')"
echo "first 8 bytes (the PNG signature):"
xxd -l 8 tiny.png
echo "89 50 4e 47 0d 0a 1a 0a — bytes 2-4 spell 'PNG' in ASCII."
echo "full dump (note the readable chunk names IHDR, tEXt, IDAT, IEND):"
xxd tiny.png

section "4. Extensions lie; magic bytes do not"
workdir="$(mktemp -d)"
cp "${samples}/hello.txt" "${workdir}/hello.png"
cp "${samples}/tiny.png" "${workdir}/tiny.txt"
echo "Renamed hello.txt -> hello.png. What does 'file' say?"
(cd "${workdir}" && file hello.png)
echo "Renamed tiny.png -> tiny.txt. What does 'file' say?"
(cd "${workdir}" && file tiny.txt)
echo "'file' reads the CONTENT (magic bytes), not the name: the fake"
echo ".png is still ASCII text, and the fake .txt is still PNG image data."
rm -rf "${workdir}"

echo
echo "=== End of byte walkthrough ==="
examples/make_samples.sh (1770 bytes)
#!/usr/bin/env bash
# Day 005 lab — (re)build the three sample files in examples/samples/.
#
# The samples ship with the repository, so you normally never need to run
# this. It exists so you can see that every byte in the samples is
# deliberate: the text files are written byte by byte with printf escape
# sequences, and the PNG is decoded from base64 text back into its exact
# 145 binary bytes.
set -euo pipefail

samples_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/samples"
mkdir -p "${samples_dir}"

# 1. hello.txt — pure ASCII: every character is exactly one byte.
printf 'Hello, world!\n' > "${samples_dir}/hello.txt"

# 2. unicode.txt — UTF-8 with 1-, 2-, 3-, and 4-byte characters:
#      \xc3\xa9          = é (U+00E9, 2 bytes)
#      \xe2\x82\xac      = € (U+20AC, 3 bytes)
#      \xf0\x9f\x8e\x89  = party-popper emoji (U+1F389, 4 bytes)
printf 'caf\xc3\xa9 costs 3 \xe2\x82\xac\nr\xc3\xa9sum\xc3\xa9\n\xf0\x9f\x8e\x89\n' \
  > "${samples_dir}/unicode.txt"

# 3. tiny.png — a real, valid 145-byte PNG: a 2x2 image whose pixels are
#    red, green, blue, and white, plus a tEXt chunk holding a comment.
#    The binary bytes are stored here as base64 text and decoded back.
png_b64='iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAOnRFWHRDb21tZW50AERheSA1IHNhbXBsZTogMngyIHBpeGVscyAtIHJlZCwgZ3JlZW4sIGJsdWUsIHdoaXRllGR9ewAAABJJREFUeNpj+M/AwADCDP+BAAAf7gX78au6dwAAAABJRU5ErkJggg=='
if printf %s "${png_b64}" | base64 -d > "${samples_dir}/tiny.png" 2>/dev/null; then
  :  # GNU coreutils and modern macOS accept -d
else
  printf %s "${png_b64}" | base64 -D > "${samples_dir}/tiny.png"  # older macOS uses -D
fi

echo "Samples written to ${samples_dir}:"
file "${samples_dir}/hello.txt" "${samples_dir}/unicode.txt" "${samples_dir}/tiny.png"
examples/samples/hello.txt (14 bytes)
Hello, world!
examples/samples/tiny.png (145 bytes)
�PNG


IHDR�Ԛs:tEXtCommentDay 5 sample: 2x2 pixels - red, green, blue, white�d}{IDATx�c����������wIEND�B`�
examples/samples/unicode.txt (32 bytes)
café costs 3 €
résumé
🎉
metadata.yml (609 bytes)
lesson_id: D005
day: 5
kind: encoding-inspection
languages: [bash]
setup_commands:
  - cd labs/sections/computing-foundations/day-005-text-images-and-sound-as-data
run_commands:
  - bash examples/inspect_bytes.sh
  - bash starter/inspect_bytes.sh
test_commands:
  - bash tests/run_tests.sh
cleanup_commands:
  - 'git checkout -- starter/inspect_bytes.sh starter/byte-detective-worksheet.md  # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 18 checks, 0 failure(s).'
requirements/README.md (936 bytes)
# Dependencies — Day 005 lab

**None beyond a POSIX shell and standard utilities.** Everything this lab
uses ships with macOS and mainstream Linux distributions:

- `bash` ≥ 3.2 (preinstalled on macOS and Linux)
- `xxd` — the hex-dump tool (ships with vim; preinstalled on macOS, and
  present on nearly every Linux distribution via the `vim`/`vim-common`
  or `xxd` package)
- `file` — identifies file types by magic bytes (preinstalled everywhere)
- `wc`, `cp`, `mktemp`, `printf` — POSIX basics
- `base64` — only needed if you re-run `examples/make_samples.sh`
- `hexdump` — optional alternative to `xxd` shown in the README

There is deliberately no `requirements.txt`/`package.json`: the sample
files are committed to the repository, so nothing needs to be downloaded
or installed. If `xxd` is missing on a minimal Linux container, install it
with your package manager (e.g. `sudo apt install xxd` on Debian/Ubuntu).
starter/byte-detective-worksheet.md (2654 bytes)
# Byte-detective worksheet — Day 005

Answer every question by **running a command and reading real bytes**, not
from memory. The commands you need are named in `starter/inspect_bytes.sh`
and demonstrated in `examples/inspect_bytes.sh`. Work from the lab
directory. Write your answers directly into this file.

## Part 1 — hello.txt (plain ASCII)

Run `xxd examples/samples/hello.txt` and `wc -c examples/samples/hello.txt`.

1. How many bytes is `hello.txt`, and how many characters do you *see* in
   the text? Explain why the two numbers relate the way they do (remember
   the invisible newline).

   Answer:

2. What is the hex value of the first byte, and which character is it?
   (Check it against the ASCII column on the right of the xxd output.)

   Answer:

3. What is the very last byte of the file in hex, and what character is
   it? Why does xxd print a dot for it in the right-hand column?

   Answer:

## Part 2 — unicode.txt (UTF-8)

Run `xxd examples/samples/unicode.txt`, then `wc -c` and `wc -m` on it.

4. Which two bytes (hex) encode the character é? How many times does that
   two-byte sequence appear in the file, and which words do they belong to?

   Answer:

5. `wc -c` and `wc -m` report different numbers for this file. Write both
   numbers and account for the difference exactly: which characters take
   2, 3, and 4 bytes?

   Answer:

6. Which four bytes (hex) encode the party-popper emoji? What do the first
   few bits of the leading byte tell a UTF-8 decoder?

   Answer:

## Part 3 — tiny.png (binary with magic bytes)

Run `xxd -l 8 examples/samples/tiny.png`, then the full
`xxd examples/samples/tiny.png`, and `file examples/samples/tiny.png`.

7. What are the first 4 bytes of the PNG in hex? Which three ASCII letters
   hide inside bytes 2–4?

   Answer:

8. `file` reports the image is "2 x 2". Find the readable chunk names in
   the full dump (IHDR, tEXt, IDAT, IEND). What human-readable sentence is
   stored inside the tEXt chunk?

   Answer:

## Part 4 — extensions versus content

Copy the samples under wrong names (use a scratch directory, e.g.
`cp examples/samples/hello.txt /tmp/hello.png`), then run `file` on the
copies.

9. When you rename `hello.txt` to `hello.png`, what does `file` say about
   it — and what does that prove about where a file's "type" really lives?

   Answer:

10. When you rename `tiny.png` to `tiny.txt` and double-click it or `cat`
    it, things look broken — yet `file` still knows what it is. Explain
    the difference between what your operating system's file manager uses
    (the extension) and what `file` uses (the magic bytes).

    Answer:
starter/inspect_bytes.sh (1918 bytes)
#!/usr/bin/env bash
# Day 005 lab — YOUR working file: X-ray the sample files with xxd and file.
#
# The skeleton below runs as-is, but each of the four exercises prints a
# placeholder line. Your job: replace each placeholder `echo` line with the
# exact command named in the comment above it, then re-run the script.
# The finished version is in examples/inspect_bytes.sh — try it yourself
# before peeking.
#
# Run from anywhere:  bash starter/inspect_bytes.sh
set -euo pipefail

samples="$(cd "$(dirname "${BASH_SOURCE[0]}")/../examples" && pwd)/samples"

echo "=== Byte detective: my own run ==="

echo
echo "--- Exercise 1: dump plain ASCII text ---"
# Replace the placeholder line below with exactly:
#   xxd "${samples}/hello.txt"
# Then answer on the worksheet: which hex byte is the capital H? Which
# byte is the invisible newline at the end?
echo "exercise 1 not completed yet"

echo
echo "--- Exercise 2: find multi-byte UTF-8 characters ---"
# Replace the placeholder line below with exactly:
#   xxd "${samples}/unicode.txt"
# Then find the two bytes of e-acute (c3 a9), the three bytes of the euro
# sign (e2 82 ac), and the four bytes of the emoji (f0 9f 8e 89).
echo "exercise 2 not completed yet"

echo
echo "--- Exercise 3: read the PNG magic bytes ---"
# Replace the placeholder line below with exactly:
#   xxd -l 8 "${samples}/tiny.png"
# The -l 8 flag limits the dump to the first 8 bytes — the PNG signature.
# On the worksheet, write down the first four bytes in hex.
echo "exercise 3 not completed yet"

echo
echo "--- Exercise 4: prove that extensions lie ---"
# Replace the placeholder line below with exactly:
#   cp "${samples}/hello.txt" "${workdir}/hello.png" && file "${workdir}/hello.png"
# The copy has a .png name but text content. Which one does `file` trust?
workdir="$(mktemp -d)"
echo "exercise 4 not completed yet"
rm -rf "${workdir}"

echo
echo "=== End of my run ==="
tests/run_tests.sh (5232 bytes)
#!/usr/bin/env bash
# Tests for the Day 005 lab. Run from the lab directory:
#   bash tests/run_tests.sh
#
# Verifies that the committed sample files are what they claim to be
# (checked by content, not by name), that the reference walkthrough runs
# and shows the expected byte sequences, and that the learner's starter
# script at least runs (held to the strict standard once every
# placeholder has been replaced).
set -u

lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
samples="${lab_dir}/examples/samples"
failures=0
checks=0

check() {
  local label="$1" ok="$2"
  checks=$((checks + 1))
  if [ "${ok}" = "yes" ]; then
    echo "  ok: ${label}"
  else
    echo "  FAIL: ${label}"
    failures=$((failures + 1))
  fi
}

contains() {  # contains <haystack> <needle>
  case "$1" in *"$2"*) return 0 ;; *) return 1 ;; esac
}

echo "Checking sample files exist ..."
for f in hello.txt unicode.txt tiny.png; do
  [ -s "${samples}/${f}" ] && check "samples/${f} exists and is non-empty" "yes" \
                           || check "samples/${f} exists and is non-empty" "no"
done

echo "Checking 'file' identifies each sample by content ..."
contains "$(file "${samples}/hello.txt")" "ASCII text" \
  && check "hello.txt is ASCII text" "yes" || check "hello.txt is ASCII text" "no"
contains "$(file "${samples}/unicode.txt")" "UTF-8" \
  && check "unicode.txt is UTF-8 text" "yes" || check "unicode.txt is UTF-8 text" "no"
contains "$(file "${samples}/tiny.png")" "PNG image data" \
  && check "tiny.png is PNG image data" "yes" || check "tiny.png is PNG image data" "no"

echo "Checking the exact bytes with xxd ..."
png_sig="$(xxd -p -l 8 "${samples}/tiny.png" | tr -d '\n')"
[ "${png_sig}" = "89504e470d0a1a0a" ] \
  && check "tiny.png starts with the PNG magic bytes 89 50 4e 47 0d 0a 1a 0a" "yes" \
  || check "tiny.png starts with the PNG magic bytes 89 50 4e 47 0d 0a 1a 0a" "no"
uni_hex="$(xxd -p "${samples}/unicode.txt" | tr -d '\n')"
contains "${uni_hex}" "c3a9" \
  && check "unicode.txt contains the 2-byte sequence c3 a9 (e-acute)" "yes" \
  || check "unicode.txt contains the 2-byte sequence c3 a9 (e-acute)" "no"
contains "${uni_hex}" "e282ac" \
  && check "unicode.txt contains the 3-byte sequence e2 82 ac (euro sign)" "yes" \
  || check "unicode.txt contains the 3-byte sequence e2 82 ac (euro sign)" "no"
contains "${uni_hex}" "f09f8e89" \
  && check "unicode.txt contains the 4-byte sequence f0 9f 8e 89 (emoji)" "yes" \
  || check "unicode.txt contains the 4-byte sequence f0 9f 8e 89 (emoji)" "no"
hello_hex="$(xxd -p "${samples}/hello.txt" | tr -d '\n')"
[ "${hello_hex}" = "48656c6c6f2c20776f726c64210a" ] \
  && check "hello.txt is exactly the 14 ASCII bytes of 'Hello, world!' + newline" "yes" \
  || check "hello.txt is exactly the 14 ASCII bytes of 'Hello, world!' + newline" "no"

echo "Checking content beats extension ..."
tmp="$(mktemp -d)"
cp "${samples}/tiny.png" "${tmp}/disguised.txt"
contains "$(file "${tmp}/disguised.txt")" "PNG image data" \
  && check "'file' still sees PNG content behind a .txt name" "yes" \
  || check "'file' still sees PNG content behind a .txt name" "no"
rm -rf "${tmp}"

echo "Running the reference walkthrough (examples/inspect_bytes.sh) ..."
if output="$(bash "${lab_dir}/examples/inspect_bytes.sh" 2>&1)"; then
  check "reference walkthrough exits successfully" "yes"
  contains "${output}" "8950 4e47" \
    && check "walkthrough output shows the PNG magic bytes" "yes" \
    || check "walkthrough output shows the PNG magic bytes" "no"
  contains "${output}" "c3a9" \
    && check "walkthrough output shows the e-acute bytes" "yes" \
    || check "walkthrough output shows the e-acute bytes" "no"
  contains "${output}" "PNG image data" \
    && check "walkthrough shows 'file' identifying the PNG" "yes" \
    || check "walkthrough shows 'file' identifying the PNG" "no"
else
  check "reference walkthrough exits successfully" "no"
  echo "${output}" | sed 's/^/    /'
fi

echo "Running your starter script (starter/inspect_bytes.sh) ..."
if s_output="$(bash "${lab_dir}/starter/inspect_bytes.sh" 2>&1)"; then
  check "starter script exits successfully" "yes"
else
  check "starter script exits successfully" "no"
  echo "${s_output}" | sed 's/^/    /'
  s_output=""
fi
if contains "$(cat "${lab_dir}/starter/inspect_bytes.sh")" "not completed yet"; then
  echo "Note: starter still has unfinished exercises — structure checked only."
  contains "${s_output}" "=== Byte detective: my own run ===" \
    && check "starter prints its header" "yes" || check "starter prints its header" "no"
else
  contains "${s_output}" "8950 4e47" \
    && check "your run shows the PNG magic bytes (exercise 3)" "yes" \
    || check "your run shows the PNG magic bytes (exercise 3)" "no"
  contains "${s_output}" "c3a9" \
    && check "your run shows the e-acute bytes (exercise 2)" "yes" \
    || check "your run shows the e-acute bytes (exercise 2)" "no"
  contains "${s_output}" "ASCII text" \
    && check "your run shows 'file' trusting content over the .png name (exercise 4)" "yes" \
    || check "your run shows 'file' trusting content over the .png name (exercise 4)" "no"
fi

echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]

Troubleshooting

Troubleshooting — Day 005 lab

command not found: xxd

xxd ships with vim and is preinstalled on macOS. On a minimal Linux system install it (sudo apt install xxd on Debian/Ubuntu, sudo dnf install vim-common on Fedora), or use hexdump -C instead — its output is equivalent for this lab (bytes on the left, ASCII on the right), only formatted slightly differently.

wc -m prints 32 instead of 24 for unicode.txt

Character counting depends on your locale. In a C/POSIX locale, wc -m counts bytes. Check with locale; if LC_CTYPE is not a UTF-8 locale, run the command as LC_ALL=en_US.UTF-8 wc -m examples/samples/unicode.txt (any UTF-8 locale works). The byte count from wc -c is locale-independent.

The emoji or accents display as boxes or question marks

That is a font/terminal limitation, not a data problem — the bytes in the file are correct (verify with xxd: the sequence f0 9f 8e 89 is there). Try a different terminal profile or font. This is a live demonstration of the lesson's point that rendering and encoding are separate layers.

file output wording differs from the expected output

Different file versions phrase results differently (UTF-8 text versus Unicode text, UTF-8 text; extra notes like with no line terminators). The tests only look for the substrings ASCII text, UTF-8, and PNG image data, which every current version prints.

Tests fail with tiny.png starts with the PNG magic bytes … FAIL

The sample was probably corrupted by an editor or a checkout that translated line endings (byte 0a0d 0a breaks binary files — that is exactly why the PNG signature contains both). Regenerate all three samples: bash examples/make_samples.sh, then re-run the tests.

base64: invalid option -- d when running make_samples.sh

Older macOS base64 uses -D (capital) to decode; the script tries -d first and falls back automatically. If you see this message but the script still ends with three file lines, everything worked.

My own text file shows ef bb bf at the start

That is a UTF-8 byte-order mark (BOM) — three bytes some Windows editors prepend. It is legal but often unwanted; many Unix tools choke on it. Save without BOM, or note it as a finding: you just diagnosed a real encoding issue from raw bytes.

Windows: bash is not recognized

Use WSL (wsl --install, then open Ubuntu and work from there — xxd and file are available). PowerShell's Format-Hex can substitute for xxd if you prefer to explore manually, but the scripts and tests assume a Unix-style shell.

Security notes

Security notes — Day 005 lab

  • What the scripts do: read the three committed sample files with file, wc, and xxd, copy two of them into a throwaway temp directory (removed at the end of the run), and print text. They make no network connections, modify no files outside mktemp -d scratch space, and change no settings.
  • Privileges: everything runs as your normal user; nothing needs sudo.
  • Why this lab is itself a security lesson: file extensions are a naming convention, not a property of the data — the rename experiment proves a .txt can be a PNG and vice versa. Attackers exploit exactly this gap (a "document" that is really an executable, an upload that lies about its type). Tools that check magic bytes, like file, judge content instead of names. Inspecting bytes with xxd is read-only and always safe; opening an unknown file with an application is what carries risk, because parsers of complex formats have historically been a rich source of vulnerabilities.
  • Privacy: the committed samples contain no personal data, and the walkthrough prints bare file names, not your local paths. If you inspect your own files while experimenting, remember a hex dump reveals everything in the file — including metadata you may not see in a normal viewer — so think before pasting dumps of personal files into a class forum.