SED Address

Learning SED

Chapter 3 — Addresses and Line Selection

What Is an Address?

Every SED command runs against the pattern space — by default, on every line of the input. An address is a restriction you place before a command to limit which lines it acts on.

The general form is:

sed 'address command' file

If you provide no address, the command runs on all lines. If you provide an address, the command only runs when SED is processing a line that matches that address.

SED supports four types of address:

  • Line number addresses — target a specific line by its position
  • The last-line address ($) — target the final line of input
  • Pattern addresses — target lines matching a regular expression
  • Range addresses — target a span of lines between a start and end address

Addresses can be combined with negation (!) to target every line except those that match, and with step notation to target every Nth line.

Line Number Addresses

The simplest address is a plain line number. SED counts lines from 1:

# Delete line 1 (commonly used to strip a header)
sed '1d' file.txt

# Substitute only on line 5
sed '5s/old/new/' file.txt

# Print only line 3
sed -n '3p' file.txt
sed '3s/foo/bar/' — line 3 address line 1: apple foo tree ← skipped (not line 3) line 2: banana foo bush ← skipped line 3: cherry foo plant ← ADDRESS MATCHES → s/foo/bar/ runs → "cherry bar plant" line 4: date foo tree ← skipped line 5: elderberry foo ← skipped

The Last-Line Address: $

$ matches the very last line of the input. Because SED processes streams, it does not know in advance how many lines there are — $ is resolved dynamically as SED reads:

# Delete the last line
sed '$d' file.txt

# Append text after the last line
sed '$a\--- end of file ---' file.txt

# Substitute only on the last line
sed '$s/old/new/' file.txt

# Print only the last line (equivalent to tail -1)
sed -n '$p' file.txt
Do not confuse $ the line address with $ the regex anchor. Inside a pattern address (/regex/), $ means end-of-line. As a standalone address before a command, $ means the last line of the file. Context tells SED which meaning applies.

Pattern Addresses

A pattern address matches any line where a regular expression is found. Enclose the regex in forward slashes:

# Delete all lines containing "DEBUG"
sed '/DEBUG/d' logfile.txt

# Substitute only on lines containing "ERROR"
sed '/ERROR/s/server1/server2/' logfile.txt

# Print only lines starting with a digit
sed -n '/^[0-9]/p' file.txt

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

# Delete lines containing only whitespace
sed '/^[[:space:]]*$/d' file.txt
sed '/ERROR/d' — pattern address 2024-01-15 INFO server started ← no match, printed as-is 2024-01-15 ERROR connection refused ← MATCHES /ERROR/ → d runs → line deleted 2024-01-15 INFO request received ← no match, printed as-is 2024-01-15 ERROR timeout after 30s ← MATCHES /ERROR/ → d runs → line deleted 2024-01-15 INFO server stopped ← no match, printed as-is

Changing the pattern address delimiter

Just like the substitute command, pattern addresses can use an alternative delimiter. This is essential when the regex itself contains forward slashes (such as file paths):

# Delete lines containing a file path — awkward with /
sed '/\/usr\/local\/bin/d' file.txt

# Same thing using \| as alternate delimiter — much cleaner
sed '\|/usr/local/bin|d' file.txt

# Using # as delimiter
sed '\#/etc/nginx#d' file.txt
Syntax for alternate pattern delimiters: Lead with a backslash, then your chosen delimiter character, then the regex, then the delimiter again: \delimregexdelim. This is different from the substitute command where you just start with the delimiter directly after s.

Range Addresses

A range address targets every line between a start address and an end address (inclusive). Start and end are separated by a comma:

start,end command

Both start and end can independently be a line number, $, or a pattern address — and they can be mixed:

Line number ranges

# Delete lines 5 through 10 (inclusive)
sed '5,10d' file.txt

# Print lines 20 to 30
sed -n '20,30p' file.txt

# Substitute from line 3 to end of file
sed '3,$s/old/new/g' file.txt

Pattern ranges

When both addresses are patterns, SED activates the range when the start pattern first matches, and deactivates it when the end pattern next matches. Lines between those two — including the matching lines themselves — are all addressed:

# Delete everything between (and including) START and END markers
sed '/START/,/END/d' file.txt

# Substitute inside an XML/HTML block
sed '/<config>/,/<\/config>/s/false/true/g' settings.xml

# Comment out a block of lines in a shell script
sed '/^# BEGIN BLOCK/,/^# END BLOCK/s/^/#/' script.sh
sed '/START/,/END/d' line 1: header text ← before range, printed line 2: START marker ← start pattern matches → range opens → deleted line 3: inside the block ← inside range → deleted line 4: still inside ← inside range → deleted line 5: END marker ← end pattern matches → deleted, range closes line 6: footer text ← after range, printed

Mixed ranges (line + pattern)

# From line 1 up to (and including) the first blank line
sed -n '1,/^$/p' file.txt

# From the line matching "BEGIN" to line 50
sed '/BEGIN/,50d' file.txt

# From line 5 to the line matching "DONE"
sed -n '5,/DONE/p' file.txt

The range end-pattern pitfall

The end pattern is only checked from the line after the start match. This means if the start pattern and end pattern both match the same line, the range will not close on that line — SED will carry on until the end pattern is found on a subsequent line (or until end of file if it never matches again).
# If START and END appear on the same line, range stays open longer than expected
printf 'before\nSTART END here\nafter\n' | sed '/START/,/END/d'
# Output: before
# "after" is ALSO deleted because the range never closed on the START END line

Step Addresses

GNU sed extends the addressing syntax with step addresses, written as first~step. This matches line first, then every stepth line after that:

# Match every other line starting from line 1 (odd lines: 1, 3, 5, …)
sed -n '1~2p' file.txt

# Match even lines (2, 4, 6, …)
sed -n '2~2p' file.txt

# Match every 3rd line starting from line 3 (3, 6, 9, …)
sed -n '3~3p' file.txt

# Delete every 5th line (5, 10, 15, …) — e.g. strip blank separator lines
sed '5~5d' file.txt

# Add a separator after every 4th line (useful for visual grouping)
sed '4~4a\---' file.txt
sed -n '1~2p' — every odd line line 1 ← 1~2 matches (1) → printed line 2 ← no match → suppressed line 3 ← 1~2 matches (1+2) → printed line 4 ← no match → suppressed line 5 ← 1~2 matches (1+4) → printed line 6 ← no match → suppressed
Step addresses are a GNU sed extension. They are not available in BSD sed or strictly POSIX environments. To select odd/even lines portably, use the hold space trick (covered in Chapter 6) or awk 'NR%2==1'.

Negating an Address with !

Appending ! immediately after an address (before the command) inverts the match — the command runs on every line that does not match the address:

# Print every line EXCEPT line 1 (skip the header)
sed -n '1!p' file.txt

# Delete every line that does NOT contain "KEEP"
sed '/KEEP/!d' file.txt

# Substitute on every line except the last
sed '$!s/old/new/g' file.txt

# Substitute on every line outside a block
sed '/START/,/END/!s/foo/bar/' file.txt

# Negate a range — act on lines OUTSIDE the range
sed '5,10!s/foo/bar/' file.txt
Negation + delete = filter. The pattern /regex/!d (delete lines that do not match) is equivalent to grep regex but processes the file in a SED pipeline — useful when you need the output to feed directly into more SED commands.

Multiple Commands on One Address — Braces

You can apply several commands to the same address by grouping them in curly braces { }. All commands inside the braces run only when the address matches:

# On lines containing "ERROR": substitute AND print
sed -n '/ERROR/ {
  s/ERROR/FAULT/
  p
}' logfile.txt

# On lines 5–15: strip leading spaces AND remove trailing punctuation
sed '5,15 {
  s/^[[:space:]]*//
  s/[[:punct:]]*$//
}' file.txt

# Inline with semicolons (same effect, less readable for complex cases)
sed '/ERROR/ { s/ERROR/FAULT/; p; }' logfile.txt

Braces can also be nested — an outer address restricts a range, and an inner address further filters within it:

# Between START and END markers, only substitute on lines containing "host"
sed '/START/,/END/ {
  /host/ s/server1/server2/
}' config.txt

Address Summary Table

Address form Matches Example
n Line number n sed '5d'
$ The last line sed '$d'
/regex/ Any line where regex matches sed '/ERROR/d'
\Xregex X Pattern with alternate delimiter X sed '\|/usr|d'
n,m Lines n through m inclusive sed '3,7d'
n,$ Line n through end of file sed '5,$d'
/pat1/,/pat2/ From first line matching pat1 to next line matching pat2 sed '/BEGIN/,/END/d'
n,/regex/ From line n to next line matching regex sed '1,/^$/d'
first~step Line first, then every stepth line (GNU) sed '1~2d'
addr! All lines that do not match addr sed '/KEEP/!d'
addr { cmds } Multiple commands under one address sed '/ERR/{s/E/e/;p}'

Practical Recipes

Remove a CSV or TSV header row

# Skip the first line (header), pass the rest to downstream commands
sed '1d' data.csv | awk -F, '{print $2}'

Extract a block between two markers

# Print only the lines between (and including) BEGIN and END
sed -n '/BEGIN/,/END/p' file.txt

# Print the block but EXCLUDE the marker lines themselves
sed -n '/BEGIN/,/END/ { /BEGIN/d; /END/d; p }' file.txt

Process only a specific function in a script

# Add debug echo before every line inside the "deploy" function
sed '/^deploy()/,/^}/ {
  /^[[:space:]]\+[^}]/ s/^/echo "DEBUG: " #/
}' deploy.sh

Remove comments and blank lines

# Strip # comments and empty lines from a config file
sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' nginx.conf

Operate only outside a quoted string block

# Substitute only on lines that do NOT look like SQL string literals
sed "/^'/!s/NULL/0/g" dump.sql

Print line numbers alongside matching lines

# Equivalent of grep -n "pattern" but done entirely in SED
sed -n '/ERROR/ {=; p}' logfile.txt
# = prints the current line number; p prints the line
42
2024-01-15 ERROR connection refused

Apply edits from line N to end of file (skip boilerplate header)

# A generated file has a 10-line header that must not be touched
sed '11,$s/v1/v2/g' generated.txt

Common Mistakes with Addresses

Confusing line 0 and line 1

# SED counts from 1 — there is no line 0
sed '0d' file.txt
# GNU sed: "invalid usage of line address 0"

# Special case: 0,/regex/ — the only place 0 is valid
# It means: start immediately (before reading line 1)
# allowing the end-pattern to match on line 1 itself
sed '0,/pattern/d' file.txt
# Deletes from start up to AND including the first line matching /pattern/
# even if that line is line 1

Pattern ranges that never close

# If END never appears after START, the range stays open to EOF
printf 'a\nSTART\nb\nc\n' | sed '/START/,/END/d'
a
# Lines b and c are also deleted — the range never closed
# Always test on sample input before running on a real file

Forgetting that ranges are re-evaluated

# A pattern range matches EVERY occurrence of the start pattern
printf 'START\na\nEND\nSTART\nb\nEND\n' | sed '/START/,/END/d'
# Both blocks are deleted — the range activates twice
# If you only want the FIRST block, use 0,/END/ after the START line

Quick Reference — Chapter 3

Address Types

n Line n (first line is 1)
$ Last line of input
/regex/ Any line matching regex
\Xregex X Pattern address with alternate delimiter X

Range and Step

n,m Lines n through m
/p1/,/p2/ From line matching p1 to line matching p2
0,/regex/ From start to first match (allows match on line 1)
first~step Line first then every stepth line (GNU sed)

Modifiers and Grouping

addr! Negate — run command on lines that do not match address
addr { cmd1; cmd2 } Run multiple commands under one address
addr1 { addr2 cmd } Nested addresses — outer restricts, inner further filters
What is coming next: Chapter 4 covers the delete, print, and quit commands in full — including how -n with p lets you use SED as a powerful line-extraction tool, and how q and Q let you stop processing early for efficiency on large files.