Text Surgery: Find, Change, Extract
"grepasks: which lines?sedanswers: make them different."
Text & Pipes taught you to find text — grep it, count it, sort it. This part teaches you to change it. sed is the single most common file-mutating command AI agents
propose — "let me just update that config" is almost always a sed one-liner — and awk is how you pull one column out of anything shaped like a table. Learn to read
these two and a huge class of agent-suggested commands stops being line noise.
sed safe to learn: it edits the stream, not the file. Like every Text & Pipes tool, sed reads text, transforms it, and prints the result — the input file is untouched
unless you explicitly say otherwise (that's Editing in Place, and it has a safety rule). You can experiment freely: the
original survives every mistake.7.1 Find & Replace — the s Command
Every editor has find-and-replace. sed is find-and-replace unplugged from the editor — it works on anything that flows: files, pipes, command output. One tiny script does it all,
and it reads like a sentence once you know the grammar:
cat menu.txt
mango smoothie — mango, ice, lime
sed 's/mango/kiwi/' menu.txt # replace the FIRST match on each line
kiwi smoothie — mango, ice, lime
sed 's/mango/kiwi/g' menu.txt # g = every match on the line
kiwi smoothie — kiwi, ice, limeRead s/mango/kiwi/g in four beats: s means substitute; mango is find this; kiwi is replace with this; g means every match on the line, not just the
first. That find half is a regular expression rather than a plain string, so ., *, ^ and $ carry the meanings
they had in Searching Text — a dot matches any character, even where you meant
a literal one. Two more pieces complete the grammar: I ignores case, and the slashes
are just delimiters — any character works, which saves you from escaping paths:
sed 's|/usr/local|/opt|' paths.txt # | instead of / — no escaping
sed 's/error/[&]/I' app.log # & = whatever matched; I = any case
[ERROR] payment timeout # "error", "Error", "ERROR" all wrappedThat | is doing sed's job, not the shell's — inside the
quotes it's a delimiter, and the same character outside them is the pipe from Text & Pipes. The single quotes are the whole difference: they hand sed its script as one untouched word (Paths),
which is why every sed script in this part wears them.
sed command bare and read its output. Happy?
Add > new-file.txt and run it again. Because sed doesn't touch the
input file, the preview is always free."Write me a sed one-liner that replaces every "staging" with "production" in deploy.txt — preview only, no file changes"
"Explain what sed 's|http://|https://|g' does, character by character"
Try It: Rebrand the Menu
menu.txt with s/mango/kiwi/g into kiwi-menu.txt — and notice the original survives
untouched. Line 1 has two mangos: you'll need g.Loading playground...
7.2 Line Surgery — Addresses, d and p
Substitution is only one of sed's commands. Picture your file as a film
strip: every line passes through sed, one frame at a time, and a command decides its fate — keep, drop, or
print. An address in front of the command selects
which frames it applies to:
d drops lines; addresses choose them sed '/DEBUG/d' app.log # drop every line matching DEBUG
sed '3d' notes.txt # drop line 3
sed '2,5d' notes.txt # drop lines 2 through 5
sed '$d' notes.txt # drop the last lineThose four are most of the address vocabulary, and two of them are worth a second look. /DEBUG/ is a regular expression tried against every line, not a plain word — the
same rules as the find half of s///. And $ here is sed's name for the last line: an address, not a variable, with nothing to do
with the $NAME expansion from Environment Variables. awk hands the same character a third job in Columns & awk.
The mirror image of dropping is printing only what you ask for. That's the p command paired with -n, which silences sed's default echo. It's the precision
tool for "show me lines 40 through 55 of that huge log" — no pager, no scrolling:
-n + p — print only the selection sed -n '40,55p' server.log # just lines 40–55
sed -n '/ERROR/p' server.log # only ERROR lines (like grep!)
sed -n '/start/,/stop/p' run.log # from a /start/ match to a /stop/ matchAddresses compose with any command — '2s/beta/B/' substitutes on line
2 only, '/alpha/s/a/A/' substitutes only on lines matching alpha. One grammar, every combination. That's the Unix philosophy again, folded inside a single
tool.
"Show me lines 100 to 120 of build.log without opening an editor"
"Write a sed command that strips every blank comment line starting with # from config.txt — preview first"
Try It: Silence the Debug Noise
app.log is drowning in DEBUG chatter. Drop those lines with '/DEBUG/d' and save the readable story as clean.log — the original
stays intact for the postmortem.Loading playground...
7.3 Editing in Place — the -i Footgun and the .bak Rule
Everything so far printed to the screen or a new file. But the command agents actually
propose is usually sed -i — edit the file in place. Same
substitution, except now it rewrites the real file, silently, with no preview and no undo. -i was the letter that stopped and asked before overwriting back in Copying; here it means the opposite of careful, which is exactly
what Getting Help meant by flag letters belonging to their command. It's also the
one sed flag worth carrying into Terminal for the AI Era, where auditing becomes a routine.
sed -i with no backup is a red-flag pattern. File it with rm -rf and curl | bash on the list of commands to read twice — Terminal for the AI Era makes that list a method. But unlike those, the fix isn't refusing — it's amending: one suffix turns the risky command into a safe one.sed -i.bak 's/http:/https:/g' config.yml
# config.yml — rewritten
# config.yml.bak — the original, untouched
diff config.yml.bak config.yml # exactly what changed, nothing else
mv config.yml.bak config.yml # instant rollback if it went wrongThe suffix goes directly after the flag — -i.bak, no space — and sed saves each original as file.bak before rewriting. It costs
nothing, it works on many files at once (sed -i.bak 's/<old>/<new>/' *.yml backs
up every one), and it turns "I hope that was right" into something you can check. That's the diff in the block above: hand it two files and it prints only the lines where
they disagree. When an agent proposes a bare -i, don't approve or reject — edit the command and add the .bak.
macOS enforces the house rule anyway. Mac and Linux carry different lineages of the same
veteran tools — BSD's on the Mac, GNU's on Linux — and BSD sed insists on that suffix: leave it off and the command fails with an error pointing nowhere near the real
problem, while GNU sed takes the bare -i and keeps no copy at
all. ps, ls and du split the same two ways, which is why a command that
worked on a colleague's machine sometimes doesn't on yours.
"You proposed sed -i without a backup — rewrite that command with -i.bak and tell me how I'd undo it"
"Mass-rename a function across all .py files here, with backups, and show me how to verify the change afterwards"
Try It: The Agent's Mass Edit
s is what
makes the connection encrypted (What Is localhost?) — but its sed -i keeps no backup. Read agent-plan.txt, amend the
command to -i.bak, run it on both config files, then open a .bak to admire your safety net.Loading playground...
7.4 Columns & awk — Pull the Field You Need
A lot of terminal output is secretly a table: log lines, CSV exports, process listings. awk splits every line into numbered fields and prints the ones you name — $1 is the first column, $2 the
second, $0 the whole line:
awk '{print $2}' table.txt # second column (splits on spaces)
awk -F, '{print $1, $3}' data.csv # -F, = split on commas; comma joins with a space
awk '/error/ {print $1}' app.log # /pattern/ runs the action on matching lines onlyThe braces are awk's action block — the work it does on every line that reaches it, with the /pattern/ guard outside
and in front. The $1 inside belongs to awk, not the shell:
it has nothing to do with the numbered script arguments in Your First Script,
and that collision is why every awk program here sits in single quotes. Swap them
for double quotes and the shell empties $1 before awk ever sees it: {print $1} arrives
as {print }, which awk reads as "print the whole line" and obeys
without complaint — the wrong answer, delivered confidently. Ask for two columns and it's a syntax
error instead. Neither one mentions quotes.
You already know cut from Text & Pipes — so which one? Honest answer: cut when a single character separates every field — a comma in a CSV, a colon
or a tab elsewhere — and awk when the spacing is ragged. awk's default split treats any run of spaces as one separator, which is
exactly what column-aligned output needs — cut would see every space as its own
empty field and hand you garbage. When you meet process listings in Everything Is a Process, awk is the tool that reads them.
awk is an entire programming language — BEGIN blocks, variables, printf. What you've just learned is the field-printing dialect, and it covers the vast majority of awk you'll ever see an agent propose. When one shows up wearing more syntax than this, that's not
a reading failure — that's your cue to ask the agent to explain it line by line."This CSV has columns name,email,plan — give me just the emails, using awk and using cut, and tell me when each tool is the better pick"
"Explain this command an agent suggested: awk '/FAIL/ {print $3}' test-output.log"
Try It: Pull the Column
signups.csv has three columns; the launch email needs one. Pull the email column
with awk -F, or cut -d, — your choice — and save it as emails.txt.Loading playground...
Your text toolbox is complete: grep finds, sed changes, awk extracts — and pipes chain them into anything. Every one of them previews
to the screen before it touches a file, which means every one of them rewards the habit this course
keeps drilling: read first, run second.
Loading challenge...