Computing Foundations › Systems Foundations: Storage, Observability, and Tooling › Day 38
Hands-on lab — Day 38: Regular Expressions
- ← Back to the Day 38 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-038-regular-expressions/
Commands
Setup
cd labs/sections/computing-foundations/day-038-regular-expressions Run
bash examples/regex_drills.sh
bash starter/regex_drills.sh Test
bash tests/run_tests.sh File tree
examples/regex_drills.sh examples/samples/data.txt expected-output/drills-output.txt expected-output/FIELDS.md expected-output/test-output.txt metadata.yml README.md requirements/README.md security.md starter/regex_drills.sh starter/regex-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 038 lab — Match Patterns with grep and sed
Lesson
- Lesson title: Regular Expressions
- Day number: 38 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-038-regular-expressions
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-038-regular-expressionswhen the site is running.
Purpose
Day 38's lesson builds regex syntax from the ground up. This lab makes it real:
you run grep -E and sed -E against a small, clearly synthetic sample file
that mixes emails, dates, phone-ish numbers, and access-log lines with IP
addresses and status codes. You extract, count, and reformat text — and confirm
your patterns are right by matching known counts.
Learning objectives
- Extract matches from text with
grep -E -o. - Count matching lines with
grep -E -c(and invert with-v). - Write patterns for emails, dates, IP addresses, and status codes, and reason about what they do and do not match.
- Capture fields and reformat them with
sed -Eand backreferences (\1,\2). - Run an automated test script and read its pass/fail output.
Prerequisites
- The Day 38 lesson (read it first — it teaches every metacharacter used here).
- Comfort running basic shell commands (Days 8–14).
- A terminal with
grepandsed(preinstalled on macOS and Linux).
Supported operating systems
- macOS — fully supported (tested on macOS with Apple Silicon, BSD grep/sed).
- Linux — fully supported (GNU grep/sed). Output is byte-for-byte identical.
- Windows — use WSL or Git Bash and follow the Linux path.
Hardware requirements
Any computer made in roughly the last 15 years. The lab only reads a small text file and prints to the terminal; it needs no particular RAM, disk, or GPU.
Required software
bash(3.2 or newer — preinstalled on macOS and Linux).grepandsed, both supporting the-Eextended-regex flag. Preinstalled on macOS and Linux.
Free and open-source options
Everything in this lab is free and ships with your OS. No account, API key, or purchase is needed; nothing reaches the network. See the lesson for free regex testers (such as regex101) you can use in a browser to build patterns interactively.
Installation
None. Copy this directory (or clone the repository) and you are ready:
cd labs/sections/computing-foundations/day-038-regular-expressions
File structure
day-038-regular-expressions/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── regex_drills.sh ← YOUR working file (4 exercises)
│ └── regex-worksheet.md ← worksheet for the practice assignment
├── examples/
│ ├── regex_drills.sh ← completed reference drills
│ └── samples/
│ └── data.txt ← the synthetic sample you match against
├── tests/
│ └── run_tests.sh ← automated checks against known counts
├── expected-output/
│ ├── drills-output.txt ← real captured run of the example drills
│ ├── test-output.txt ← real captured run of the tests
│ └── FIELDS.md ← the exact expected counts, all platforms
├── requirements/
│ └── README.md ← dependency statement (none beyond the OS)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished drills first
bash examples/regex_drills.sh
## 2. Your task: fill in the 4 exercises in the starter, then run it
bash starter/regex_drills.sh
## 3. Check your work against the known counts
bash tests/run_tests.sh
What the commands do
bash examples/regex_drills.sh— runs four worked drills againstexamples/samples/data.txt: extract emails (grep -E -o), count dated lines (grep -E -c), extract IP-ish addresses (grep -E -o), and reformat a date withsed -Ebackreferences. Each pattern is explained line by line in the script's comments.bash starter/regex_drills.sh— the same idea with four numbered exercises to complete: match phone-ish numbers, extract status codes from the log lines, count non-comment lines, and reorder a day-first date with capture groups. Each has a placeholder (WRITE_YOUR_PATTERN_HERE) and a hint.bash tests/run_tests.sh— runs the reference patterns against the sample and checks the counts (6 emails, 9 dated lines, 5 IPs, 5 status codes, 16 non-comment lines) and thesedreformat, then confirms the example script exits cleanly.
Expected output
See expected-output/drills-output.txt — a
real captured run. The key numbers (exact, because the sample is fixed):
emails extracted ............ 6
lines with a YYYY-MM-DD date . 9
IP-ish addresses ............ 5
status codes (log lines) .... 5 (200, 401, 200, 404, 500)
non-comment lines ........... 16
sed reformat 2026/07/12 ..... 2026-07-12
expected-output/FIELDS.md lists the same counts
and confirms they are identical on macOS and Linux.
Validation steps
- Run
bash examples/regex_drills.sh— it must print six emails, the number9, five IP addresses, and2026-07-12. - Complete the four exercises in
starter/regex_drills.sh(replace everyWRITE_YOUR_PATTERN_HERE) and run it — Exercise 1 should print 5 phone numbers, Exercise 2 the five status codes, Exercise 3 the count16, and Exercise 42026-07-12. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 8 checks, 0 failure(s). The command exits 0 on success
and non-zero on any failure, so it can run in CI. It uses only the committed
sample and makes no network calls.
Cleanup
Nothing to clean up: the scripts only read the sample and print to the terminal.
To reset your work, restore the starter files from git:
git checkout -- starter/regex_drills.sh starter/regex-worksheet.md.
Troubleshooting
See troubleshooting.md for the full list: missing -E,
BSD-vs-GNU sed differences on macOS/Linux, \d not matching, and patterns
that match too much.
Security notes
See security.md. Short version: the scripts read a synthetic
local file, make no network calls, need no elevated privileges, and never
execute matched text — a habit you should keep (never eval what a regex
extracts).
Extension exercises
- Replace a broad
.*with a narrow class. On a line like<a href="one"> and <b href="two">, comparegrep -E -o '<.*>'(one greedy match) withgrep -E -o '<[^>]*>'(two tag matches) and explain the difference in terms of backtracking. - Write a single pattern that matches only valid-looking status codes in the
200–599 range (
[2-5][0-9][0-9]) and confirm it still finds all five. - Add a fifth drill that redacts every email address in the sample, replacing
the local part with
***usingsed -Eand a capture group for the domain.
Navigation
- Previous day: Day 37 — Debuggers, Linters, and Formatters
(
labs/sections/computing-foundations/day-037-debuggers-linters-and-formatters/). - Next day: Day 39 — Data Storage: Files, Databases, Object Storage, and
Caches (
labs/sections/computing-foundations/day-039-data-storage-files-databases-object-storage/).
Expected output
FIELDS.md
# Expected results (all platforms)
The committed sample `examples/regex_drills.sh` runs against
`examples/samples/data.txt`, which is fixed, so these counts are exact and the
same on macOS and Linux:
| Drill / pattern | Result |
| --- | --- |
| Email addresses extracted | **6** |
| Lines containing a `YYYY-MM-DD` date | **9** |
| IP-ish addresses extracted | **5** |
| Status codes pulled from log lines | **5** (200, 401, 200, 404, 500) |
| Non-comment lines (do not start with `#`) | **16** |
| `sed` reformat of `2026/07/12` | **`2026-07-12`** |
Files in this directory are real captures from the authoring machine
(macOS, BSD grep/sed, 2026-07-12):
- `drills-output.txt` — the full output of `bash examples/regex_drills.sh`.
- `test-output.txt` — the full output of `bash tests/run_tests.sh`, ending in
`8 checks, 0 failure(s).`
## Platform note (macOS vs Linux)
The patterns in this lab deliberately use POSIX character classes (`[0-9]`,
`[[:alnum:]]`, `[[:alpha:]]`) and explicit `{n}` counts, and both `grep -E` and
`sed -E`. These behave identically under BSD tools (macOS) and GNU tools
(Linux), so the output above is byte-for-byte the same on both. The only
differences you would hit are with GNU-only shorthands like `\d` or `\+`, which
this lab avoids on purpose — see `troubleshooting.md`.
drills-output.txt
=== Drill 1: extract every email address ===
ada.lovelace@example.com
grace.hopper@example.org
alan.turing@example.net
katherine.johnson@example.com
edsger.dijkstra@example.org
mission.control@example.net
=== Drill 2: count lines that contain a YYYY-MM-DD date ===
9
=== Drill 3: extract every IP-ish address ===
192.168.1.44
10.0.0.7
172.16.5.9
192.168.1.44
10.0.0.7
=== Drill 4: reformat a date field with capture groups (sed backreferences) ===
2026-07-12
=== Done. Compare these results with the counts in the README. ===
test-output.txt
Checking the committed sample exists ...
ok: <repo>/labs/sections/computing-foundations/day-038-regular-expressions/examples/samples/data.txt
Checking known counts from the sample ...
ok: email addresses extracted (6)
ok: lines with a YYYY-MM-DD date (9)
ok: IP-ish addresses extracted (5)
ok: status codes from log lines (5)
ok: non-comment lines (16)
ok: sed backreference reformat (2026-07-12)
Checking the example drills script runs clean ...
ok: examples/regex_drills.sh exits 0 (yes)
ok: example output shows reformatted date (yes)
8 checks, 0 failure(s).
Source files
examples/regex_drills.sh (2948 bytes)
#!/usr/bin/env bash
# Day 038 lab — worked regex drills with grep -E and sed -E.
#
# This is the completed REFERENCE script. It runs entirely offline against the
# committed sample file examples/samples/data.txt and demonstrates the four
# everyday regex jobs: extract, count, extract-with-groups, and reformat.
# Read each pattern's explanation, run the script, then rebuild it yourself in
# starter/regex_drills.sh.
#
# We use grep -E (extended regex) and sed -E throughout, and POSIX character
# classes like [0-9] and [[:alnum:]] rather than the PCRE shorthands \d and \w,
# because the POSIX forms behave the same in BSD grep/sed (macOS) and GNU
# grep/sed (Linux). See troubleshooting.md for the dialect differences.
set -euo pipefail
# Resolve the sample file relative to this script, so the drills run from any
# working directory.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
sample="${script_dir}/samples/data.txt"
echo "=== Drill 1: extract every email address ==="
# Pattern, piece by piece:
# [[:alnum:]._%+-]+ one or more letters/digits/dot/underscore/percent/plus/hyphen (the local part)
# @ a literal @ sign
# [[:alnum:].-]+ one or more letters/digits/dot/hyphen (the domain name)
# \. a LITERAL dot (escaped — an unescaped . matches any character)
# [[:alpha:]]{2,} two or more letters (the top-level domain)
# -o prints only the matched text, not the whole line.
grep -E -o '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' "${sample}"
echo
echo "=== Drill 2: count lines that contain a YYYY-MM-DD date ==="
# [0-9]{4}-[0-9]{2}-[0-9]{2} four digits, hyphen, two digits, hyphen, two digits.
# -c counts MATCHING LINES rather than printing them. Note: this checks the
# SHAPE of a date, not whether the month and day are real calendar values.
grep -E -c '[0-9]{4}-[0-9]{2}-[0-9]{2}' "${sample}"
echo
echo "=== Drill 3: extract every IP-ish address ==="
# ([0-9]{1,3}\.){3} a group of "1-3 digits then a literal dot", repeated 3 times
# [0-9]{1,3} a final 1-3 digit number
# This matches the SHAPE of an IPv4 address. It also matches impossible octets
# like 999.999.999.999 — good enough to extract from logs you trust, not to
# validate untrusted input.
grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' "${sample}"
echo
echo "=== Drill 4: reformat a date field with capture groups (sed backreferences) ==="
# Turn YYYY/MM/DD into YYYY-MM-DD.
# ([0-9]{4})/([0-9]{2})/([0-9]{2}) capture year, month, day into groups 1, 2, 3
# \1-\2-\3 rebuild them joined by hyphens
# We use '#' as the sed delimiter instead of the usual '/' so the slashes in the
# date do not clash with sed's own syntax.
echo '2026/07/12' | sed -E 's#([0-9]{4})/([0-9]{2})/([0-9]{2})#\1-\2-\3#'
echo
echo "=== Done. Compare these results with the counts in the README. ==="
examples/samples/data.txt (1096 bytes)
# Synthetic sample data for the Day 38 regex lab.
# Every name, address, phone number, and IP below is fictional and for practice only.
# Lines beginning with '#' are comments.
#
# --- Section 1: contacts ---
Ada Lovelace <ada.lovelace@example.com> joined 2026-01-15
Grace Hopper <grace.hopper@example.org> joined 2026-02-03
Alan Turing <alan.turing@example.net> joined 2026-03-21
Katherine Johnson, katherine.johnson@example.com, phone 555-0142
Edsger Dijkstra, edsger.dijkstra@example.org, phone (555) 867-5309
#
# --- Section 2: phone list (synthetic) ---
Support line: 555-0100
Fax (old): 555-0199
Mobile: 555-234-5678
#
# --- Section 3: access log (synthetic IPs and status codes) ---
192.168.1.44 - - [2026-07-12] "GET /health" 200
10.0.0.7 - - [2026-07-12] "POST /login" 401
172.16.5.9 - - [2026-07-12] "GET /report" 200
192.168.1.44 - - [2026-07-13] "GET /data.csv" 404
10.0.0.7 - - [2026-07-13] "GET /health" 500
#
# --- Section 4: mixed notes ---
Renewal due 2026-11-30 for account #4821 (pending)
No date on this line, just some plain text.
Contact backup: mission.control@example.net
metadata.yml (602 bytes)
lesson_id: D038
day: 38
kind: conversion-exercises
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-038-regular-expressions
run_commands:
- bash examples/regex_drills.sh
- bash starter/regex_drills.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/regex_drills.sh starter/regex-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, BSD grep/sed), bash tests/run_tests.sh → 8 checks, 0 failure(s).'
requirements/README.md (871 bytes)
# Dependencies — Day 038 lab
**None beyond a POSIX shell with `grep` and `sed`.** This lab has zero
installable dependencies:
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution)
- `grep` with the `-E` (extended regex) option — preinstalled everywhere
- `sed` with the `-E` (extended regex) option — preinstalled on macOS (BSD sed)
and Linux (GNU sed)
There is deliberately no `requirements.txt`/`package.json`: every tool used
ships with the operating system, nothing reaches the network, and no account or
API key is required. The lab only *reads* the committed sample file and prints
to your terminal.
If `grep -E` or `sed -E` is somehow missing (extremely minimal containers),
install `grep` and `sed` from your package manager (`apt install grep sed` on
Debian/Ubuntu). See `../troubleshooting.md` for BSD-vs-GNU differences.
starter/regex_drills.sh (2768 bytes)
#!/usr/bin/env bash
# Day 038 lab — YOUR regex drills.
#
# Four numbered exercises. Each has a pattern placeholder set to
# WRITE_YOUR_PATTERN_HERE. Replace each placeholder with a working extended
# regular expression, then run: bash starter/regex_drills.sh
#
# Use grep -E and sed -E, and prefer POSIX classes ([0-9], [[:alpha:]]) over the
# PCRE shorthands \d and \w so your patterns work in both BSD (macOS) and GNU
# (Linux) tools. The completed reference is in examples/regex_drills.sh — try it
# yourself before peeking. Expected results are in the README's "Expected output".
set -uo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
sample="${script_dir}/../examples/samples/data.txt"
echo "=== Exercise 1: extract every phone-ish number ==="
# Phone numbers in the sample look like 555-0142, 555-0100, 867-5309, 234-5678.
# Write a pattern for "three digits, a hyphen, four digits" and pass it to
# grep -E -o. Expected: 5 matches.
EX1_PATTERN="WRITE_YOUR_PATTERN_HERE" # hint: [0-9]{3}-[0-9]{4}
grep -E -o "${EX1_PATTERN}" "${sample}" || echo "(no matches yet — fill in EX1_PATTERN)"
echo
echo "=== Exercise 2: extract just the status codes from the log lines ==="
# The access-log lines end with a quoted request then a 3-digit status code,
# e.g. "GET /health" 200 . Beware: several phone lines also end in digits, so
# a naive [0-9]{3}$ would match those too. First keep only log lines (they
# contain a quote followed by a space and three digits at end of line), then
# extract the trailing 3-digit code. Expected: 200 401 200 404 500.
EX2_SELECT="WRITE_YOUR_PATTERN_HERE" # hint: " [0-9]{3}$ (selects log lines)
EX2_EXTRACT="WRITE_YOUR_PATTERN_HERE" # hint: [0-9]{3}$ (pulls the code)
grep -E "${EX2_SELECT}" "${sample}" | grep -E -o "${EX2_EXTRACT}" || echo "(fill in EX2 patterns)"
echo
echo "=== Exercise 3: count how many lines are NOT comments ==="
# Comment lines begin with '#'. Use grep with -c (count) and -v (invert match)
# and an anchor to count every line that does NOT start with '#'. Expected: 16.
EX3_PATTERN="WRITE_YOUR_PATTERN_HERE" # hint: ^#
grep -E -c -v "${EX3_PATTERN}" "${sample}" || echo "(fill in EX3_PATTERN)"
echo
echo "=== Exercise 4: reformat a day-first date with capture groups ==="
# Turn a day-first date like 12-07-2026 into ISO order 2026-07-12 by capturing
# the three fields and rebuilding them in a new order with backreferences.
# Fill in EX4_FIND (three capture groups) and EX4_REPLACE (\3-\2-\1).
EX4_FIND="WRITE_YOUR_PATTERN_HERE" # hint: ([0-9]{2})-([0-9]{2})-([0-9]{4})
EX4_REPLACE="WRITE_YOUR_PATTERN_HERE" # hint: \3-\2-\1
echo '12-07-2026' | sed -E "s#${EX4_FIND}#${EX4_REPLACE}#"
echo
echo "=== Done. Compare with the README's Expected output. ==="
starter/regex-worksheet.md (1870 bytes)
# Regex worksheet — Day 038
Fill this in as you work through the lab. Write your patterns in the code spans
and record the counts you get. The sample file is
`../examples/samples/data.txt` (relative to this `starter/` directory).
## 1. Write the pattern for an email address
Write an extended-regex pattern that extracts every email address from the
sample with `grep -E -o`.
- My pattern: `______________________________________________`
- Command I ran: `grep -E -o '______' ../examples/samples/data.txt`
- Number of addresses it found: `____`
> One-sentence note on what your pattern does NOT guarantee (hint: think about
> what "a valid email address" really requires):
>
> _______________________________________________________________
## 2. Write the pattern for a YYYY-MM-DD date
Write a pattern that matches an ISO date such as `2026-07-12`.
- My pattern: `______________________________________________`
- One thing about dates this pattern does NOT verify: `__________________`
## 3. Count how many lines match a pattern of your choice
Pick any pattern (an IP address, a phone number, lines containing `@`, lines
that are comments, …) and use `grep -E -c` to count the matching lines.
- Pattern I chose: `______________________________________________`
- What it is meant to match (in words): `__________________________`
- Command: `grep -E -c '______' ../examples/samples/data.txt`
- Count returned: `____`
## 4. Reflection (2–3 sentences)
Which was harder to get right: describing the shape you wanted, or avoiding
matches you did not want? Give one example from this lab where a pattern was
"too loose" (matched something unintended) or "too tight" (missed something)
and how you fixed it.
_______________________________________________________________________
_______________________________________________________________________
tests/run_tests.sh (2737 bytes)
#!/usr/bin/env bash
# Tests for the Day 038 regex lab. Run from anywhere:
# bash tests/run_tests.sh
#
# Verifies that the reference drills extract the KNOWN correct counts from the
# committed synthetic sample, and that the example script runs clean. No
# network, no writes outside stdout. Exits 0 on success, non-zero on any
# failure, so it is safe for CI.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
sample="${lab_dir}/examples/samples/data.txt"
example="${lab_dir}/examples/regex_drills.sh"
failures=0
checks=0
check() {
local label="$1" expected="$2" actual="$3"
checks=$((checks + 1))
if [ "${expected}" = "${actual}" ]; then
echo " ok: ${label} (${actual})"
else
echo " FAIL: ${label} — expected '${expected}', got '${actual}'"
failures=$((failures + 1))
fi
}
echo "Checking the committed sample exists ..."
if [ ! -f "${sample}" ]; then
echo " FAIL: sample file not found at ${sample}"
echo
echo "1 checks, 1 failure(s)."
exit 1
fi
echo " ok: ${sample}"
echo "Checking known counts from the sample ..."
# Drill 1: email addresses (expected 6)
emails="$(grep -E -o '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' "${sample}" | grep -c .)"
check "email addresses extracted" "6" "${emails}"
# Drill 2: lines containing a YYYY-MM-DD date (expected 9)
dated="$(grep -E -c '[0-9]{4}-[0-9]{2}-[0-9]{2}' "${sample}")"
check "lines with a YYYY-MM-DD date" "9" "${dated}"
# Drill 3: IP-ish addresses (expected 5)
ips="$(grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' "${sample}" | grep -c .)"
check "IP-ish addresses extracted" "5" "${ips}"
# Status codes from log lines (expected 5): select log lines, then pull the code
codes="$(grep -E '" [0-9]{3}$' "${sample}" | grep -E -o '[0-9]{3}$' | grep -c .)"
check "status codes from log lines" "5" "${codes}"
# Non-comment lines (expected 16)
noncomment="$(grep -E -c -v '^#' "${sample}")"
check "non-comment lines" "16" "${noncomment}"
# Drill 4: sed reformat of a slash date to a hyphen date
reformatted="$(echo '2026/07/12' | sed -E 's#([0-9]{4})/([0-9]{2})/([0-9]{2})#\1-\2-\3#')"
check "sed backreference reformat" "2026-07-12" "${reformatted}"
echo "Checking the example drills script runs clean ..."
if out="$(bash "${example}" 2>&1)"; then
check "examples/regex_drills.sh exits 0" "yes" "yes"
else
check "examples/regex_drills.sh exits 0" "yes" "no"
echo "${out}" | sed 's/^/ /'
fi
# The example output must contain the reformatted date it demonstrates.
if echo "${out}" | grep -q '2026-07-12'; then
check "example output shows reformatted date" "yes" "yes"
else
check "example output shows reformatted date" "yes" "no"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 038 lab
grep: invalid option or my metacharacters are treated literally
You almost certainly omitted -E. Basic grep (BRE) requires a backslash
before +, ?, {, (, and | to make them special; grep -E (extended,
ERE) treats them as special directly. Every pattern in this lab assumes -E.
sed errors on macOS but the same command works on Linux (BSD vs GNU)
macOS ships BSD sed; most Linux distributions ship GNU sed. They differ:
-Efor extended regex: both accept-E. (GNU also accepts-r; BSD does not. Use-Efor portability — this lab does.)- In-place editing: GNU is
sed -i 's/…/…/' file; BSD requires an argument,sed -i '' 's/…/…/' file. This lab never edits files in place, so you will not hit this, but it is the most common cross-platformsedsurprise. - Shorthands: GNU sed understands
\+,\?, and (in some builds)\d; BSD sed does not. This lab uses only[0-9],[[:alpha:]], and explicit{n}counts, which behave the same on both.
If a sed command from elsewhere fails on macOS, first rewrite any \d as
[0-9] and any \+ as + under -E.
\d matches nothing
Many command-line tools do not support the PCRE shorthand \d. Use [0-9] or
the POSIX class [[:digit:]]. The \d, \w, \s shorthands live in Python
and JavaScript, not in every grep/sed.
The IP or status-code pattern matches something unexpected
This is the "too loose" failure mode and it is the whole point of the lab. For
example, [0-9]{3}$ matches the last three digits of a phone number as well as
a status code — that is why Exercise 2 first selects log lines with
" [0-9]{3}$ before extracting. Tighten the pattern or add context (a
neighbouring literal, an anchor) until it matches only what you mean.
Permission denied when running a script
Run it through bash explicitly: bash starter/regex_drills.sh. You do not need
to chmod +x anything. If you prefer ./starter/regex_drills.sh, first run
chmod +x starter/regex_drills.sh.
The sed date reformat prints the input unchanged
Your pattern did not match, so sed left the line alone. Check that the field
counts match the input exactly ({4} vs {2}) and that you used # as the
delimiter so the slashes in the date do not clash with sed's syntax:
sed -E 's#([0-9]{4})/([0-9]{2})/([0-9]{2})#\1-\2-\3#'.
Windows
Use WSL (wsl --install, then open Ubuntu) and follow the Linux path, or use
Git Bash. The GNU tools inside WSL behave exactly as described for Linux.
Security notes
Security notes — Day 038 lab
- What the scripts do: read one committed, synthetic sample file
(
examples/samples/data.txt) and print matches and counts to your terminal. They make no network connections, write no files (except the tests you choose to redirect), and change no settings. - The sample is fictional. Every name, email, phone number, and IP address
in the sample is invented for practice (
example.com/.org/.netare reserved documentation domains; the IPs are private-range). Nothing in this lab touches real personal data. - Never
evalmatched text. A recurring real-world danger with regex is taking text a pattern extracted and executing it as a command (eval "$match") or interpolating it into a shell command. Matched text is untrusted input; treat it as data, never as code. This lab only prints matches — it never runs them — and you should keep that habit. - Watch for catastrophic backtracking (ReDoS). If you later run patterns
over untrusted input, avoid nested quantifiers over overlapping classes (the
classic
(a+)+$shape), prefer specific classes like[^"]*to broad.*, and use tools or engines with a timeout. A single crafted input can otherwise freeze a process. - Privileges: everything runs as your normal user. Nothing here needs
sudo; if any tutorial asks you tosudoa script you have not read, stop and read it first.