โšก Onwuachi Control Plane

sed Cheatsheet

KB-CLI-001: sed Cheatsheet

Where this lives: content/kb/infrastructure/general/sed-cheatsheet.md


What sed actually is

sed = Stream EDitor. It reads input line by line, applies a command to each line, and prints the result. It never “knows” about the whole file at once unless you tell it to โ€” by default it’s a one-line-at-a- time machine. That’s the single most important mental model: sed processes a stream, not a document.

file โ†’ [sed reads line 1] โ†’ applies command โ†’ prints result โ†’ next line...

The command you’ve used most: substitution

sed 's/OLD/NEW/' file

Breaking down s/OLD/NEW/:

The flag you actually want most of the time: g (global)

sed 's/OLD/NEW/g' file

Without g, a line with OLD...OLD only replaces the first one. This is the #1 sed mistake โ€” forgetting g and wondering why only half your matches changed.


-i โ€” edit the file in place

This is what you’ve been using throughout this session:

sed -i 's/OLD/NEW/g' file.yml

Without -i, sed only prints the result to your terminal โ€” the file itself is untouched. This is actually useful for previewing a change safely before committing to it:

sed 's/OLD/NEW/g' file.yml          # preview only, file unchanged
sed -i 's/OLD/NEW/g' file.yml       # now actually edit the file

Always preview risky substitutions first without -i.


Real examples from this session

1. Standardizing scrape target IPs in prometheus.yml:

sed -i 's/172.17.0.1:9115/127.0.0.1:9115/g' prometheus.yml

Find 172.17.0.1:9115, replace every occurrence with 127.0.0.1:9115.

2. Fixing a file path (note the escaped slashes and escaped special chars):

sed -i 's|/etc/prometheus/rules/\*\.yml|/opt/prometheus/rules/*.yml|g' prometheus.yml

Here we used | instead of / as the delimiter โ€” because the pattern itself contains / characters (file paths). If you used / as the delimiter here, every / in the path would need escaping (\/etc\/...), which gets unreadable fast. sed lets you pick any delimiter โ€” |, #, ,, anything not in your pattern โ€” to avoid this. Also note \* and \. โ€” in regex, * and . are special characters (. = any character, * = “zero or more of the previous thing”), so to match them literally you escape them with \.

3. Updating GitHub Actions variable names:

sed -i 's/vars\.S3_BUCKET}}/vars.S3_BUCKET_ONWUA_PORTFOLIO_SITE}}/g' deploy-portfolio.yml

The \. before S3_BUCKET escapes the literal dot (vars.S3_BUCKET) so sed doesn’t interpret it as “any character.”


The four delimiter rule

sed 's/path/to/file/new'      # BREAKS โ€” sed sees 4 fields, not 2
sed 's|path/to/file|new|'     # WORKS โ€” | as delimiter, / is just text

Rule of thumb: if your pattern or replacement contains /, switch the delimiter to something that doesn’t appear in either side โ€” |, #, ~, , are common choices.


Special regex characters you must escape to match literally

CharacterRegex meaningTo match literally
.any single character\.
*zero or more of previous\*
/(only special if it’s your delimiter)use a different delimiter, or \/
[ ]character class\[ \]
^start of line\^
$end of line\$

This is why install_haproxy.cfg style paths with dots in filenames need \. โ€” otherwise domain.map would also match domainXmap, domain_map, anything with one character where the dot is.


Other sed patterns worth knowing

Delete lines matching a pattern:

sed -i '/pattern/d' file

Deletes every line containing pattern. Used for cleaning up dead config lines.

Append text after a matching line:

sed -i '/match this line/a\
new line of text' file

Print only lines in a range (like grep but line-number aware):

sed -n '10,20p' file

-n suppresses normal output, 10,20p prints only lines 10-20.

Multiple substitutions in one command (semicolon-separated):

sed -i 's/foo/bar/g; s/baz/qux/g' file

Replace text

sed -i ’s/old/new/g’ file.md

ie. vim %s/old/new/g

Delete a line

sed -i ‘/pattern/d’ file.md

Insert after a line

sed -i ‘/pattern/a New text’ file.md

Insert before a line

sed -i ‘/pattern/i New text’ file.md

Print a range

sed -n ‘1,20p’ file.md

ie head -n 20 file.md

Multiple edits

sed -i -e ’s/a/b/’ -e ’s/c/d/’ file.md


Multiple edits at once

Instead of

vim :%s/foo/bar/g :%s/apple/orange/g

You can do

sed -i
-e ’s/foo/bar/g’
-e ’s/apple/orange/g’
-e ’s/bourbon/whiskey/g’
file.md


Why DevOps people love sed

Imagine you decide six months from now:

Every bourbon article needs

image = ""

Instead of editing 40 files…

find content/kb/bourbon
-name “*.md”
-exec sed -i ‘/type = “bourbon-bottle”/a
image = "" ’ {} +

Done.


Combine with grep

Find every file missing an image

grep -L ‘image =’ *.md

Now fix them

for f in $(grep -L ‘image =’ *.md) do sed -i ‘/type =/a image = “”’ “$f” done

Populate common fields automatically:

sed -i
-e ’s/draft = true/draft = false/’
-e ’s/rating = 0/rating = 88/’
-e ’s|image = “"|image = “/images/bourbon/knob-creek-12.png”|’
content/kb/bourbon/bottles/knob-creek-12.md

When sed isn’t the right tool

sed is line-based and doesn’t understand file structure (YAML nesting, JSON, HCL blocks). For anything beyond “replace this exact text,” you risk corrupting structured files. That’s exactly what happened with the malformed prometheus.yml node job earlier โ€” a sed edit (or a bad copy/paste) left an orphaned ec2_sd_configs: key with no parent. For structural changes to YAML/JSON/HCL, edit by hand in an editor (vi) or use a tool that understands the format (yq for YAML, jq for JSON).

Rule of thumb: sed for single-line text substitution. Editor or format-aware tool for anything touching structure, indentation, or nesting.


Quick reference table

GoalCommand
Replace first match per linesed 's/old/new/' file
Replace all matches per linesed 's/old/new/g' file
Edit file in placesed -i 's/old/new/g' file
Preview without editingsed 's/old/new/g' file (no -i)
Use different delimiter`sed ’s
Delete matching linessed -i '/pattern/d' file
Print line range onlysed -n '10,20p' file
Chain multiple editssed -i 's/a/b/g; s/c/d/g' file
Match literal dotsed 's/file\.txt/x/'
Match literal asterisksed 's/\*/x/'

The diagnostic command you ran this session

diff main.tf main.tf-bak

Not sed, but related โ€” diff compares two files line by line and shows what changed. < lines are from the first file (main.tf), > lines are from the second (main.tf-bak). This is how we discovered the CloudFront Function additions never actually landed in your edited file โ€” the diff showed comment-only changes and one unrelated policy addition, but no aws_cloudfront_function block.

diff file1 file2
# <  = only in file1
# >  = only in file2
# matching lines aren't shown at all

System Context

โ† Back to Kb