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/:
s= substitute- First
/.../= pattern to find (supports regex) - Second
/.../= replacement text - No trailing flag = replace only the first match per line
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
| Character | Regex meaning | To 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
| Goal | Command |
|---|---|
| Replace first match per line | sed 's/old/new/' file |
| Replace all matches per line | sed 's/old/new/g' file |
| Edit file in place | sed -i 's/old/new/g' file |
| Preview without editing | sed 's/old/new/g' file (no -i) |
| Use different delimiter | `sed ’s |
| Delete matching lines | sed -i '/pattern/d' file |
| Print line range only | sed -n '10,20p' file |
| Chain multiple edits | sed -i 's/a/b/g; s/c/d/g' file |
| Match literal dot | sed 's/file\.txt/x/' |
| Match literal asterisk | sed '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