sed Cheatsheet

⏱ 25 min read • TECH

All examples target GNU sed on Linux unless a recipe is explicitly described as portable or BSD/macOS-specific. Shell blocks use Bash; sed blocks contain scripts to save and run with sed -f.

Related: AWK fundamentals, advanced AWK.

Execution Model: Pattern Space and Hold Space

ConceptWhat happens
Pattern spaceThe current working buffer; initially one input line without its trailing newline
Hold spaceA separate persistent buffer, initially empty; explicitly managed with h/H/g/G/x
CycleRead a line, run applicable commands in order, then automatically print unless -n or a control command changes this
AddressSelects which input lines or pattern spaces a command applies to
Multiline pattern spaceCreated with commands such as N or G; it contains embedded newlines
printf 'old old\nkeep\n' | sed 's/old/new/'
# new old
# keep

printf 'old old\nkeep\n' | sed -n 's/old/new/p'
# new old

-n suppresses automatic printing, and the substitution’s p flag prints only after a match. Without -n, p normally produces an additional copy. A pattern normally sees only the current buffer, not the entire file.

Basic Syntax

sed [options] 'command' file(s)
sed [options] -e 'command1' -e 'command2' file(s)
sed [options] -f script-file file(s)

Common Options

OptionDescription
-e scriptAdd commands in ‘script’ to the set of commands to be executed
-f script-fileAdd commands from ‘script-file’ to the set of commands to be executed
-nSuppress automatic printing of pattern space
-i[suffix]GNU: edit files in place; optional attached suffix makes a backup
-E / -rExtended regular expressions; prefer -E across modern GNU/BSD systems
-sGNU: restart numbering/ranges for each input file
--follow-symlinksGNU: edit a symlink target with -i instead of replacing the link
-l NGNU: wrapping width of the l command’s diagnostic output
-zGNU: NUL-delimited records instead of newline-delimited lines
-uGNU: minimize input/output buffering for pipelines
--debugGNU: display parsed commands and execution trace
--sandboxGNU: reject execution and file I/O commands (e, r, w and related forms)
--posixGNU: disable GNU extensions for compatibility checks

Basic Commands

Substitution (Most Common Use)

s/pattern/replacement/[flags]

Substitution Flags

FlagDescription
gGlobal replacement (replace all occurrences)
pPrint the pattern space if substitution was made
3 (any positive number)Replace that numbered occurrence only
w fileWrite the result to a file if substitution was made
i / IGNU: case-insensitive matching
3gGNU: replace the third and all later matches
eGNU: execute the substituted pattern space as a shell command
m / MGNU: multiline anchor mode; see the multiline examples

The g flag applies within each pattern space. A numbered substitution is not a line number. In GNU sed, combining a number with g skips earlier matches and replaces the rest; other implementations need not use the same semantics. The e flag executes data as code: use it only for deliberately constructed, trusted commands (examples below).

Printing Commands

CommandDescription
pPrint the current pattern space
=Print the current line number
lPrint escaped/unambiguous pattern space; $ marks the end
PPrint only up to the first embedded newline
FGNU: print the current input filename

Delete Commands

CommandDescription
dDelete the pattern space; start next cycle
DRemove the first line from a multiline buffer and restart the script without reading input; if no newline exists, behave like d
zGNU: empty the pattern space; unlike d, continue the current cycle

Address Ranges

AddressMatches
nLine number n
$Last line
/pattern/Lines matching pattern
addr1,addr2Range from addr1 to addr2
addr1,+nGNU: addr1 and the next n lines
addr1,~nGNU: from addr1 through the next following line whose number is a multiple of n
first~stepGNU: every step-th line beginning with first
0,/pattern/GNU: from start through the first match, including a match on line 1
address!commandApply command everywhere except the addressed lines/range

Ranges include both endpoints. In an ordinary two-address range beginning at an input line, a regex second address is first checked on the line after the start match. The GNU 0,/pattern/ form is the special first-line case shown above. Thus /start/,/end/ does not end on the same line it starts, even if that line contains both words. An unmatched end extends the range to EOF. Ranges can start again later.

seq 1 8 | sed -n '3,+2p'    # 3, 4, 5 (GNU)
seq 1 8 | sed -n '3,~4p'    # 3, 4 (GNU)
seq 1 8 | sed -n '2~3p'     # 2, 5, 8 (GNU)
printf 'foo\nfoo\n' | sed '0,/foo/s/foo/bar/' # Change only first foo (GNU)

Common Patterns and Operations

Basic Text Manipulation

# Replace first occurrence of 'old' with 'new' on each line
sed 's/old/new/' file

# Replace all occurrences of 'old' with 'new' on each line
sed 's/old/new/g' file

# Replace the 3rd occurrence of 'old' with 'new' on each line
sed 's/old/new/3' file

# Case-insensitive replacement
sed 's/old/new/gi' file

# Delete lines containing 'pattern'
sed '/pattern/d' file

# Delete blank lines
sed '/^$/d' file

# Delete leading whitespace (spaces, tabs)
sed 's/^[[:blank:]]*//' file

# Delete trailing whitespace
sed 's/[[:blank:]]*$//' file

# Remove comments (lines starting with #)
sed '/^#/d' file

# Delete both leading and trailing whitespace
sed 's/^[[:blank:]]*//;s/[[:blank:]]*$//' file

/^$/ removes only completely empty lines. Use /^[[:space:]]*$/d to remove whitespace-only lines too. [[:blank:]] matches horizontal blanks (typically spaces/tabs); [[:space:]] also includes other whitespace. These classes are locale-sensitive.

Line Operations

# Print only lines 1 to 5
sed -n '1,5p' file

# Print line 52
sed -n '52p' file

# Print from line with 'start' to line with 'end'
sed -n '/start/,/end/p' file

# Print every 3rd line starting from line 1
sed -n '1~3p' file

# Insert text before line 3
sed '3i\New text before line 3' file

# Append text after line 3
sed '3a\New text after line 3' file

# Replace the content of line 3
sed '3c\New content for line 3' file

# Delete lines from 3 to 7
sed '3,7d' file

The one-line i\text, a\text, and c\text forms above are GNU-friendly. For portable scripts, put a backslash and a literal newline before the text, as shown in the script-file example near the end. i and a insert text; they do not create a nonexistent addressed input line in an empty file.

Multiple Commands

# Multiple commands with -e
sed -e 's/old/new/g' -e '/pattern/d' file

# Multiple commands with semicolons
sed 's/old/new/g; /pattern/d' file

# Using a command script from a file
sed -f commands.sed file

Working with Multiple Files

# Edit file in-place without backup
sed -i 's/old/new/g' file

# Edit file in-place with backup extension .bak
sed -i.bak 's/old/new/g' file

# Process multiple files
sed 's/old/new/g' file1 file2 file3

Advanced Commands and Techniques

Hold Space Operations

CommandDescription
hCopy pattern space to hold space
HAppend pattern space to hold space
gCopy hold space to pattern space
GAppend hold space to pattern space
xExchange pattern space and hold space

Flow Control

CommandDescription
nPrint pattern space if auto-print is enabled, then read the next line
NAppend the next input line to pattern space with an embedded newline
b labelBranch to label; if label is omitted, branch to end of script
t labelBranch after a successful substitution since the last input read or t/T test; reset the test flag
T labelGNU: branch when there has been no such successful substitution
qQuit; automatically print current buffer unless -n
QGNU: quit without printing current buffer
q N, Q NGNU: quit with exit status N
:labelSpecify the location of label for b and t commands

Examples Using the Hold Space

# Reverse the order of lines
sed -n '1!G;h;$p' file

# Duplicate each line
sed 'p' file

# Double-space a file
sed 'G' file

# Triple-space a file
sed 'G;G' file

# Print matching line AND the next line; consumed context is not re-tested
sed -n '/pattern/{p;n;p}' file

# Join pairs of lines
sed '$!N;s/\n/ /' file

# Add line numbers to a file
sed = file | sed 'N;s/\n/\t/'

The guarded pair join preserves an odd final line. Reversing via hold space stores the whole file and repeatedly copies it; tac is often more efficient for large files. The next section explains how N/P/D form a sliding window.

Multi-line Processing

# Replace a pattern that spans multiple lines
sed ':a;$!{N;ba};s/pattern\n[^\n]*another/replacement/g' file

# Delete a block of text between two patterns
sed '/start/,/end/d' file

# Print paragraphs containing 'pattern'
sed -n '/./{H;$!d;};x;/pattern/{s/^\n//;p;}' file

The whole-file loop guards N at EOF so a one-line input still reaches the replacement. It holds the entire file in memory. The paragraph recipe treats empty lines as separators, prints matching paragraphs without the accumulator’s leading blank line, and handles a final paragraph without a blank separator.

Using Regular Expressions

# Using basic regular expressions
sed 's/[0-9]\{3\}/number/g' file

# Using extended regular expressions
sed -E 's/[0-9]{3}/number/g' file

# GNU word operators: replace words starting with pre
sed -E 's/\bpre\w+/WORD/g' file

# GNU word boundaries: replace whole words only
sed -E 's/\bword\b/WORD/g' file

# GNU NUL-delimited mode: with no NUL bytes, the entire file is one record
# Dot can then match the embedded newlines (without M mode)
sed -z 's/pattern.*/replacement/' file

# Capture groups
sed -E 's/(prefix)-(suffix)/\1_\2/' file

Special Characters and Escaping

Pattern Characters: BRE versus ERE

Single-quoted shell arguments below pass backslashes directly to sed. Do not add another shell escaping layer unless using double quotes.

MeaningBRE (default)ERE (-E)
Any character..
Start / end of pattern space^ / $^ / $
Zero or more**
One or more\{1,\} (portable); \+ (GNU)+
Optional\{0,1\} (portable); \? (GNU)?
Exactly / at least / bounded repeats\{3\} / \{3,\} / \{3,5\}{3} / {3,} / {3,5}
Capturing group\(text\)(text)
AlternativeGNU: foo\|barfoo|bar
Backreference to captured group\1 through \9\1 through \9 (supported by GNU/BSD sed)
Character class / negation[abc] / [^abc][abc] / [^abc]
Literal dot, star, bracket[.] / [*] / [[][.] / [*] / [[]
Literal parentheses()\(\)
Literal backslash\\\\

& is ordinary in the pattern, but special in the replacement. \n is a newline escape, not a numbered backreference. A normal line cycle removes the record’s trailing newline, so \n can match only an embedded newline introduced by multiline processing (or present inside a NUL-delimited record).

Replacement Special Characters

SyntaxMeaning
&Entire matched text
\1, \2, …, \9Captured group, with the same numbering in BRE and ERE
\&Literal ampersand
\\Literal backslash
\/Literal / when / is the command delimiter
\n, \tGNU replacement escapes for newline and tab; use a backslash plus literal newline for a portable inserted newline
\L, \U, \EGNU: lowercase/uppercase following text; stop case conversion
\l, \uGNU: lowercase/uppercase next character
printf 'price=12\n' | sed 's/[0-9][0-9]*/[&]/'
# price=[12]
printf 'alice:admin\n' | sed -E 's/^([^:]+):(.+)$/user=\1 role=\2/'
# user=alice role=admin
printf 'A&B\n' | sed 's/A\&B/C\&D/'
# C&D
printf '/srv/old/app\n' | sed 's|/srv/old/|/srv/new/|'
# /srv/new/app
printf 'Alice SMITH\n' | sed -E 's/^([^ ]+) (.*)$/\L\1\E \L\2/'
# alice smith (GNU)

A different delimiter avoids escaping path slashes, but that delimiter still needs escaping when it occurs in the regex or replacement. Use [$] for a literal dollar sign, \^ for a literal caret, and [[] for a literal opening bracket. [^] is not a valid example of a negated character class. For example, sed 's/[$]/USD/g' replaces literal dollar signs.

Platform-specific Notes

GNU sed vs BSD sed (macOS)

  • GNU sed (Linux) accepts -i without a suffix; an attached suffix such as -i.bak creates a backup
  • BSD sed (macOS) takes a suffix argument with -i, even if empty: -i ''
  • GNU sed has more extensions (e.g., -z for null data processing)
  • Use gsed on macOS after installing GNU sed via Homebrew

-i is not a POSIX option. GNU-specific recipes using -z, -s, T, Q, z, R, W, F, case-conversion escapes, e, or GNU regex operators should be run with gsed on macOS unless the local manual explicitly confirms support. Modern GNU/BSD sed both support -E; escape conventions and optional command arguments still vary.

Common Tricks for Cross-platform Compatibility

# Bash: branch for the system sed commonly supplied by Linux/macOS
if [[ "$OSTYPE" == "darwin"* ]]; then
  sed -i '' 's/old/new/g' file  # macOS
else
  sed -i 's/old/new/g' file     # Linux/GNU
fi

# Or use separate extension everywhere
sed -i.bak 's/old/new/g' file

Tips and Tricks

Performance Considerations

  • Sed streams ordinary line-by-line edits with small buffers; whole-file or reverse-order recipes require much more memory. Compare awk, tac, or other tools when the task fits them better.
  • Use -E for readable patterns; it is not inherently faster.
  • GNU -s restarts numbering/ranges per file; -i implies separate-file processing.
  • Addresses can avoid unnecessary substitutions. Use q after a known final line when later input is not needed.

Debugging sed Scripts

# Show what sed is doing
sed --debug 's/old/new/g' file  # GNU sed only

# Print the pattern space at each step
sed -n 'l' file

# Inspect the literal script, and use Bash xtrace when diagnosing expansion
printf '%s\n' 's/old/new/g'
# Enable temporarily around the command being diagnosed: set -x; ...; set +x

Creative Uses

The following newline and escape recipes use GNU sed. The CRLF conversion assumes line-oriented text; appending \r directly to existing CRLF input would double the carriage return. See the idempotent normalization recipe below.

# Convert Windows/DOS newlines to Unix
sed 's/\r$//' file

# Convert Unix newlines to Windows/DOS
sed 's/$/\r/' file

# Extract trailing hash bars: each input line must end with at least one #
sed -nE 's/^(.*[^#])?(#+)$/\2/p' file | sed 's/^/|/'

# Remove ANSI SGR color/style sequences (GNU escapes; not every ANSI sequence)
sed 's/\x1b\[[0-9;]*m//g' file

# Caesar cipher (rotate by 13)
sed 'y/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM/' file

Common One-liners

# Insert line numbers
sed = file | sed 'N;s/\n/\t/'

# Double-space a file
sed G file

# Delete trailing whitespace
sed 's/[[:blank:]]*$//' file

# Insert 5 blank lines before line 23 (hold space is initially empty here)
sed '23{x;p;p;p;p;p;x}' file

# Reverse order of lines (GNU sed)
sed '1!G;h;$!d' file

# Center each line in 80 columns: Bash, printf, wc and sed
# ASCII/byte-width text; long lines are preserved without padding
while IFS= read -r line || [[ -n "$line" ]]; do
  bytes=$(LC_ALL=C printf '%s' "$line" | wc -c)
  padding=$(( (80 - bytes) / 2 ))
  (( padding > 0 )) || padding=0
  printf '%*s%s\n' "$padding" '' "$line"
done < file | sed 's/[[:blank:]]*$//'

# Find duplicate lines globally: sort + count + sed formatting
# sed retains counts > 1; blank lines are valid data too
LC_ALL=C sort file | uniq -c | sed -n '/^[[:blank:]]*1[[:blank:]]/!{s/^[[:blank:]]*//;p;}'

Example sed Script (save as commands.sed)

# Remove comments and blank lines
/^#/d
/^$/d

# Replace 'foo' with 'bar'
s/foo/bar/g

# Insert a line before any line containing 'INSERT_POINT'
/INSERT_POINT/i\
This line will be inserted before the INSERT_POINT line

# Append a line after any line containing 'APPEND_POINT'
/APPEND_POINT/a\
This line will be appended after the APPEND_POINT line

# Replace any line containing 'REPLACE_ME' completely
/REPLACE_ME/c\
This line replaces the entire line containing REPLACE_ME

# Explicit output for IMPORTANT lines; do not combine with default printing
/IMPORTANT/p

Run sed -n -f commands.sed file to print only the selected IMPORTANT lines plus text explicitly emitted by i/a/c. Those commands emit their own text even with -n. With normal auto-printing, the final /IMPORTANT/p would duplicate matching lines; remove it when the intent is to print the entire edited stream.

Practical Recipes: Input, Command and Result

Update a Configuration Value and Keep Its Formatting

Create service.env:

# PORT=3000
PORT = 3000  # local service
HOST=localhost
sed -E 's/^([[:blank:]]*PORT[[:blank:]]*=[[:blank:]]*)[0-9]+([[:blank:]]*(#.*)?)$/\18080\2/' service.env

Output:

# PORT=3000
PORT = 8080  # local service
HOST=localhost

The anchored expression ignores the commented key, preserves spacing and the optional trailing comment, and requires a numeric value. \1 is the first group; 8080 is literal replacement text. This is a recipe for this line-based format, not an interpreter for arbitrary shell assignments.

Preview first, then make the same edit with a backup:

sed -E -i.bak 's/^([[:blank:]]*PORT[[:blank:]]*=[[:blank:]]*)[0-9]+([[:blank:]]*(#.*)?)$/\18080\2/' service.env
diff -u service.env.bak service.env
# diff exits 1 when it finds changes; that is expected here

Change a Key Only in One INI Section

Input app.ini:

[server]
port=3000
host=localhost
[metrics]
port=9090
sed '/^\[server\]$/,/^\[/{s/^[[:blank:]]*port[[:blank:]]*=.*/port=8080/;}' app.ini

Only the server port becomes 8080; the metrics port stays 9090. The range includes the following section header, but the anchored key substitution does not match that header. This recipe assumes exact, unindented section headers and intentionally replaces the whole matching value line, including any inline comment. Repeated [server] sections are all processed.

Insert a Missing Key: Bash + grep + sed

Input settings.env contains simple KEY=value lines, with at most one active TIMEOUT key. A readable file and valid existing syntax are assumed.

if grep -q '^TIMEOUT=' settings.env; then
  sed -i.bak 's/^TIMEOUT=.*/TIMEOUT=30/' settings.env
else
  # GNU sed: append after the last input line
  # An empty input file has no last line, so handle that case in Bash
  if [[ -s settings.env ]]; then
    sed -i.bak '$a\TIMEOUT=30' settings.env
  else
    cp -- settings.env settings.env.bak
    printf '%s\n' 'TIMEOUT=30' > settings.env
  fi
fi

Existing keys are updated; missing keys are appended once. The grep check decides which edit to perform, and sed handles the textual update. GNU sed’s a command supplies a separating newline even if the original final line had none.

Extract or Replace a Marked Block

Input report.txt:

before
BEGIN
alpha
beta
END
after
# Include delimiters
sed -n '/^BEGIN$/,/^END$/p' report.txt

# Exclude delimiters; output is alpha then beta
sed -n '/^BEGIN$/,/^END$/{/^BEGIN$/d; /^END$/d; p;}' report.txt

# Replace the whole block, including its markers (portable c syntax)
sed '/^BEGIN$/,/^END$/c\
REDACTED' report.txt
# before
# REDACTED
# after

These ranges are flat: they do not balance nested markers. If END is missing, the range extends to EOF. Use a parser or an explicit state machine when nesting is part of the input format.

Keep Context before a Match: Hold Space in Practice

printf 'request A\nERROR timeout\nrequest B\nOK\n' |
  sed -n '/ERROR/{x;p;x;p;};h'
# request A
# ERROR timeout

At the end of each cycle h remembers the current line. On a match, x;p;x;p prints the saved previous line, restores the match, and prints it. A match on the very first line prints an empty previous buffer; omit that blank by guarding the first p with a nonempty test if needed. Consecutive errors can print overlapping context.

Branch, Repeat or Fall Back with b, t and T

# Repeat until no adjacent commas remain; t branches only after a substitution
printf 'a,,,,b,,c\n' | sed ':again; s/,,/,/g; t again'
# a,b,c

# If the key conversion succeeds, skip the fallback prefix
printf 'name=alice\nunstructured\n' |
  sed -E 's/^name=(.*)$/user=\1/; t done; s/^/unparsed: /; :done'
# user=alice
# unparsed: unstructured

# GNU T takes the failure branch; b jumps unconditionally
printf 'id=42\nid=oops\n' |
  sed -E 's/^id=([0-9]+)$/ID:\1/; T unchanged; b done; :unchanged; s/^/INVALID: /; :done'
# ID:42
# INVALID: id=oops

A t/T test resets the substitution-success flag. A successful substitution counts even if its replacement text is identical to the match, so a loop such as :a;s/x/x/;ta does not terminate on a line containing x. Always make a repeating transformation progress toward a stopping condition.

Remove Adjacent Duplicates with a Sliding Window

printf 'a\na\na\nb\nb\nc\n' | sed '$!N; /^\(.*\)\n\1$/!P; D'
# a
# b
# c

N makes a two-line window, P emits its first line only when the pair differs, and D slides the window by one line. The final guard preserves a single trailing line. This removes adjacent duplicates only; it also handles empty input and repeated blank lines.

For duplicate counts across the entire file:

printf 'pear\napple\npear\napple\napple\nkiwi\n' |
  LC_ALL=C sort | uniq -c |
  sed -n '/^[[:blank:]]*1[[:blank:]]/!{s/^[[:blank:]]*//;p;}'
# 3 apple
# 2 pear

Here sort groups equal lines, uniq -c counts them, and sed filters/formats the counts. Sorting changes the original order. LC_ALL=C makes grouping byte-oriented; text containing NUL requires a consistently NUL-aware pipeline instead.

Redact Tokens in Line-Based Logs

printf 'user=alice token=abc123 status=200\nuser=bob token=qwerty status=403\n' |
  sed -E 's/(token=)[^[:space:]]+/\1[REDACTED]/g'
# user=alice token=[REDACTED] status=200
# user=bob token=[REDACTED] status=403

The token contract here is token= followed by a nonempty whitespace-free value. Values with spaces, JSON escaping or other syntax need a pattern/parser suited to that format. Use a fixed replacement rather than interpreting log data with the e flag.

Extract Structured Fields from a Known Log Format

printf '2026-11-03 INFO user=alice duration=42ms\n2026-11-03 DEBUG heartbeat\n' |
  sed -nE 's/^([0-9]{4}-[0-9]{2}-[0-9]{2}) (INFO|WARN|ERROR) user=([^ ]+) duration=([0-9]+)ms$/\1,\2,\3,\4/p'
# 2026-11-03,INFO,alice,42

-n plus s///p both extracts matching fields and drops unrelated lines. The date expression checks shape, not calendar validity. This output is safe as simple comma-delimited text only under the shown field restrictions.

printf 'Read [GNU sed](https://www.gnu.org/software/sed/) today.\n' |
  sed -E 's/\[([^]]+)\]\((https?:\/\/[^)]+)\)/\1 <\2>/g'
# Read GNU sed <https://www.gnu.org/software/sed/> today.

This handles simple inline links without nested brackets, parenthesized URLs, escapes or optional titles. It is a targeted text transformation, not a full Markdown parser.

Normalize BOM, CRLF and Horizontal Whitespace

# GNU sed: remove a UTF-8 BOM only at the beginning, CR at line end, and trailing blanks
LC_ALL=C sed '1s/^\xEF\xBB\xBF//;s/\r$//;s/[[:blank:]]*$//' windows.txt

# Idempotent CRLF output: strip one existing CR before appending one
sed 's/\r$//;s/$/\r/' file > windows-output.txt

# Replace each tab with four spaces (does not align actual tab stops)
sed 's/\t/    /g' file

# Collapse every run of empty lines to one; GNU sed EOF behavior
sed '/^$/{N;/^\n$/D;}' file

Use sed -n 'l' to inspect carriage returns/tabs and od -An -tx1 when checking exact output bytes. For tab-stop-aware expansion, combine expand -t 4 file with subsequent sed transformations instead of replacing every tab with a fixed number of spaces.

Join Backslash-Continued Lines

Input continued.txt:

command \
  --flag \
  value
standalone
sed ':join; /\\$/{ $!{s/\\$//; N; s/\n[[:blank:]]*/ /; b join;};}' continued.txt
# command  --flag  value
# standalone

This assumes a final backslash marks continuation and keeps preceding spaces, so the sample retains more than one space between words. A backslash on the last input line is preserved. This does not implement shell quoting rules; it joins a known continuation convention.

Multiline Anchors: GNU M Mode

printf 'alpha\nbeta\n' | sed -z 's/^/> /gM'
# > alpha
# > beta
# >  (the final newline creates an empty line start; no final newline follows this marker)

# Same idea without a trailing newline in the input
printf 'alpha\nbeta' | sed -z 's/^/> /gM'
# > alpha
# > beta  (no final newline added)

# A dot matches embedded newline by default, but not with M
printf 'a\nb' | sed -z 's/a.b/X/'    # X
printf 'a\nb' | sed -z 's/a.b/X/M'   # a then b, unchanged

M makes ^/$ recognize boundaries inside the pattern space and prevents . from matching a newline. -z changes the input record delimiter; it does not itself mean multiline-anchor mode. On a newline-only file without NUL, -z buffers the whole input and can consume substantial memory.

Additional Commands: Files, Transformations and Exit Status

CommandPurpose
y/source/destination/Transliterate characters one-to-one; sets must have equal length
r filenameQueue the whole file for output after the current line
R filenameGNU: queue one next line from a file each time the command runs
w filenameWrite the current pattern space to a file
W filenameGNU: write only the first line of the pattern space
e commandGNU: execute a fixed shell command; without an argument execute pattern space
v 4.0GNU: require GNU extensions and at least the specified version

r/R output is not inserted into pattern space, so later substitutions in the same cycle do not transform it. File arguments for r/w normally extend to the script’s newline; use a separate -e or a literal newline rather than appending ; another-command to the filename.

Insert a File after a Marker

Create template.txt and fragment.txt:

# template.txt
header
INCLUDE
footer
# fragment.txt
first inserted line
second inserted line

The # filename labels above explain the fixtures; leave them out of the actual files.

sed '/^INCLUDE$/r fragment.txt' template.txt
# header
# INCLUDE
# first inserted line
# second inserted line
# footer

# Read the fragment, then remove the marker from normal output
sed -e '/^INCLUDE$/r fragment.txt' -e '/^INCLUDE$/d' template.txt
# header
# first inserted line
# second inserted line
# footer

GNU sed silently ignores a file it cannot open with r/R; check readability in the surrounding shell when missing fragments must be an error.

Interleave Lines from Another File (GNU R)

printf 'A\nB\nC\n' > letters.txt
printf '1\n2\n' > digits.txt
sed 'R digits.txt' letters.txt
# A
# 1
# B
# 2
# C

Once the auxiliary file reaches EOF, R adds no more lines. GNU -s rewinds R files between main input files.

Split Matching Records into a Separate File

printf 'INFO ready\nERROR failed\nWARN retry\nERROR timeout\n' > app.log
sed -n '/^ERROR/w errors.log' app.log
# stdout: empty
# errors.log contains the two ERROR lines

# Match + duplicate to file while printing the complete normal stream
sed '/^ERROR/w errors.log' app.log

A w destination is created/truncated before normal processing, even if no line matches. Use a distinct output file, never the input path. To retain existing destination content, append with shell redirection instead:

sed -n '/^ERROR/p' app.log >> errors-archive.log

To record only lines on which a particular substitution succeeded, use the substitution’s w flag:

sed -n 's/^WARN /WARNING /w changed.log' app.log
# changed.log contains WARNING retry for the sample app.log
# Unchanged INFO/ERROR records are not written

Write Only the First Line of a Multiline Buffer (GNU W)

printf 'a\nb\nc\nd\n' | sed -n -e 'N' -e 'W pair-heads.txt'
# pair-heads.txt: a then c
# P would print those first lines to stdout; W sends them to the file

Transliteration, Case Conversion and Empty Buffers

printf 'a-b-c\n' | sed 'y/-/_/'
# a_b_c

# GNU: capitalize the first character of each whitespace-delimited word
printf 'alice bob\n' | sed -E 's/(^|[[:blank:]])([[:alpha:]])/\1\u\2/g'
# Alice Bob

# z clears the selected buffer but still permits subsequent commands
printf 'keep\nsecret\n' | sed '/secret/z'
# keep, followed by a blank line
# /secret/d would remove that line entirely

y transforms every character from its source set; it does not take regexes, ranges like [a-z], or a g flag. Case conversion uses the current locale.

Stop Early and Signal Whether a Match Was Found

# Print lines 1-5 and stop reading
sed '5q' file

# GNU: quit before printing a STOP marker
sed '/^STOP$/Q' file

# GNU: status 0 for READY, custom status 7 for no match in nonempty input
# Readability and empty-input checks are handled by Bash
if [[ ! -r file ]]; then
  printf '%s\n' 'Cannot read file' >&2
elif [[ ! -s file ]]; then
  printf '%s\n' 'No READY marker (empty input)'
elif sed -n '/^READY$/{q 0;};$q 7' file; then
  printf '%s\n' 'READY found'
else
  status=$?
  if (( status == 7 )); then
    printf '%s\n' 'No READY marker'
  else
    printf 'sed error: %s\n' "$status" >&2
  fi
fi

For simple presence checks, grep -q is usually clearer. This example demonstrates early termination and a custom exit status; the empty-file branch is needed because sed executes no commands on empty input. GNU sed normally uses 1 for invalid scripts, 2 for unreadable input files and 4 for fatal I/O/runtime errors. A custom status of 7 distinguishes the no-match case from those errors. The surrounding Bash snippet prints the outcome; it does not itself propagate that status as a script exit code.

Execute a Deliberately Constructed Command (GNU e)

# Fixed trusted command; e writes its output, -n suppresses the original line
printf 'placeholder\n' | sed -n 'e date -u +%Y-%m-%d'

# Only a fixed numeric fixture is turned into a fixed-format command
printf '42\n' | sed -E 's/^[0-9]+$/printf "value=%s" &/e'
# value=42

# Refuse shell/file-I/O extensions when evaluating a transformation-only script
sed --sandbox 's/old/new/g' file

The substitution e flag invokes a shell after a successful substitution. It strips the command output’s final newline, and ordinary auto-printing supplies the record terminator. Do not apply it to arbitrary log text or user-supplied commands. GNU ep and pe flags can print different stages because evaluation and printing occur in flag order.

Bash Integration and Batch Editing

Put Shell Data into a Replacement without Changing Its Meaning

This Bash recipe treats @@VALUE@@ as a fixed placeholder and accepts a single-line replacement. It escapes the replacement’s backslash, ampersand and chosen | delimiter; it does not turn an arbitrary search string into a literal regex.

value='A&B | C:\tmp\demo'
case "$value" in
  *$'\n'*) printf '%s\n' 'Expected a single-line replacement' >&2; exit 1 ;;
esac
escaped=$(printf '%s' "$value" | sed 's/[\&|]/\\&/g')
printf 'value=@@VALUE@@\n' | sed "s|@@VALUE@@|$escaped|g"
# value=A&B | C:\tmp\demo

Single quotes protect a literal sed script from Bash. Double quotes are needed above so $escaped expands. Parameter expansion does not re-run shell syntax embedded in the value; sed still interprets its own replacement syntax, which is why escaping is required. Environment variables and Bash strings cannot carry NUL bytes.

Process Filenames with Spaces Using find -exec

# Preview: transformations are printed to stdout for matching .conf files
find ./configs -type f -name '*.conf' -exec sed 's/old[.]example/new.example/g' {} +

# GNU: edit each file in place with an attached backup suffix
find ./configs -type f -name '*.conf' -exec sed -i.bak 's/old[.]example/new.example/g' {} +

find -exec ... {} + passes filenames as arguments without splitting on spaces/newlines. The ./configs prefix also prevents a filename from being mistaken for an option. Both commands use old[.]example to match the literal hostname; a bare dot in the regex would match any single character.

Keep File Boundaries and Numbering Explicit (GNU -s and F)

printf 'a1\na2\n' > first.txt
printf 'b1\nb2\n' > second.txt

# Combined input stream: $ selects only b2
sed -n '$p' first.txt second.txt

# Separate files: $ selects a2 and b2
sed -s -n '$p' first.txt second.txt

# File name, per-file line number, and contents for each file's first line
sed -s -n '1{F;=;p;}' first.txt second.txt
# first.txt
# 1
# a1
# second.txt
# 1
# b1

Without -s, numbered addresses and ranges can span file boundaries. GNU -i already implies separate-file processing. F displays the filename; = displays the input line number, not an index within the multiline pattern space.

Keep NUL-Delimited Filenames Intact (GNU -z)

# Remove only the leading ./ from each NUL-delimited pathname
find . -type f -print0 | sed -z 's|^\./||' > paths.nul
# GNU xargs -0 can consume the transformed records; never split them on newlines

Every record is a filename, even if it contains a newline. Output remains NUL-delimited. This example produces names, not edits to the files themselves.

Live Logs with Line Buffering

tail -n 0 -F app.log | sed -u -n '/ERROR/{s/token=[^[:space:]]*/token=[REDACTED]/g;p;}'

tail -F follows the named file across rotations; GNU sed -u reduces buffering. The stream runs until interrupted. The token-format assumptions are the same as in the redaction recipe.

Generate a Separate Output and Inspect the Difference

sed -E 's/[[:blank:]]+$//' input.txt > output.txt
diff -u input.txt output.txt

-n and -i together write only explicitly printed output: sed -ni 's/old/new/' file can therefore produce an empty file. Use -i 's/old/new/' for a complete edited stream, or explicitly print the intended records when filtering in place.

Do not redirect to the same file being read: the shell truncates it before sed starts. GNU -i edits through a temporary file and replacement rename; it is not an append operation and can change link behavior. Keep backup files until the edit has been reviewed; repeated .bak runs can replace an older backup.

Ready-to-Run Cleanup Script

Create cleanup.sed:

# GNU sed: normalize CRLF, trim trailing horizontal blanks
s/\r$//
s/[[:blank:]]*$//

# Drop blank lines and full-line # comments, even if indented
/^[[:space:]]*$/d
/^[[:blank:]]*#/d

# Convert a deliberately simple legacy key
s/^legacy_host=/host=/

Input legacy.conf:

  # deployment settings
legacy_host=localhost

port=8080
sed -f cleanup.sed legacy.conf
# host=localhost
# port=8080

Use sed -i.bak -f cleanup.sed legacy.conf for the same cleanup in place. Keep this script format-specific: for JSON/YAML/XML, use a parser when the intended edit depends on document structure rather than a known textual pattern.

Resources for Further Learning

Comments

© 2025 CodeByMe.de | Engineering