TerminalVibes / Text Surgery
Part 7

Text Surgery: Find, Change, Extract

"grep asks: which lines? sed answers: 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.

Important
The idea that makes 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:

s/old/new/g — substitute, find this, replace with this, every match on the line
Your first substitution
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, lime

Read 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:

Delimiters are your choice; & echoes the match
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 wrapped

That | 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.

Tip
Preview to the screen, then redirect. The same habit as echo-the-glob from Copy, Move, Delete: run the 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.
Vibe it

"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

Marketing renamed the mango everything. Rewrite 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.
Rebrand the Menu

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:

Every line passes through; addresses pick the frames, commands decide their fate
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 line

Those 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/ match

Addresses 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.

Vibe it

"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.
Silence the Debug Noise

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 -iedit 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.

-i rewrites the real file — .bak keeps the original in the drawer
Caution
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.
The house rule: -i.bak
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 wrong

The 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.

Vibe it

"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

The agent's plan switches the site to https — worth doing, since the 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.
The Agent's Mass Edit

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:

Tables are just text — awk pulls the column you ask for
Fields, separators, guards
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 only

The 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.

Tip
Real 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.
Vibe it

"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.
Pull the Column

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.

Challenge: Promote the Stack

Loading challenge...