Control the Cycle
Learning SED
Chapter 4 — Delete, Print and Quit Commands
Three Commands That Control the Cycle
While the substitute command transforms text, the three commands in this chapter control something more fundamental — whether a line appears in the output at all, and when SED stops reading. Together with the -n flag, they give you precise control over what gets printed and when processing ends.
| Command | What it does | Effect on cycle |
|---|---|---|
d |
Delete the pattern space | Skips the rest of the script and the default print; jumps immediately to the next cycle |
p |
Print the pattern space | Outputs the pattern space now (in addition to the default print at end of cycle) |
q |
Quit after this line | Prints the pattern space (unless -n), then exits immediately |
Q |
Quit without printing | Exits immediately without printing the pattern space (GNU sed) |
The Delete Command: d
The d command deletes the pattern space and immediately starts the next cycle — it does not just suppress output, it aborts all remaining commands for the current line and jumps straight to reading the next line. No further commands in the script run after d fires.
Basic deletion
# Delete every line containing "TODO" sed '/TODO/d' notes.txt # Delete blank lines sed '/^$/d' file.txt # Delete lines containing only whitespace sed '/^[[:space:]]*$/d' file.txt # Delete lines shorter than 3 characters (e.g. stray single-char lines) sed '/^.\{0,2\}$/d' file.txt # Delete the first line (strip a header row) sed '1d' data.csv # Delete the last line sed '$d' file.txt # Delete a range of lines sed '3,7d' file.txt
Deleting comment lines and cleaning config files
# Remove shell-style # comments sed '/^[[:space:]]*#/d' script.sh # Remove both comments AND blank lines in one pass sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' nginx.conf # Remove inline comments (everything from # to end of line) sed 's/[[:space:]]*#.*$//' config.txt # Note: this uses s// not d — it removes the comment but keeps the line
Deleting between markers
# Delete a block including its opening and closing tags sed '/<!-- BEGIN SKIP -->/,/<!-- END SKIP -->/d' page.html # Delete from a pattern to end of file sed '/^### Appendix/,$d' README.md # Delete from line 1 to the first blank line (strip email headers) sed '1,/^$/d' email.txt
Why d short-circuits the script
Because d aborts the rest of the script for that line, the order of your commands matters. A command placed after d in the same address block will never run on lines where d fires:
# The substitution on line 2 will NEVER run for lines matching /DEBUG/ sed '/DEBUG/ { d s/foo/bar/ # unreachable — d already ended this cycle }' file.txt # If you need to transform before deleting, put the transform first sed '/DEBUG/ { s/DEBUG/INFO/ # runs first d # then deletes (so the substitution is pointless here) }' file.txt # In practice, if you're deleting the line anyway, transforms before d are useless
The Print Command: p
The p command prints the current contents of the pattern space to standard output. Crucially, SED also prints the pattern space automatically at the end of each cycle (the default print). This means that without -n, using p causes each matching line to appear twice.
This double-printing behaviour is not a bug — it becomes genuinely useful in specific patterns, and the fix when you don't want it is always -n.
The -n flag — suppressing the default print
The -n flag (sometimes called "silent mode" or "quiet mode") tells SED not to print the pattern space automatically at the end of each cycle. With -n active, the only output you get is from explicit p commands (or the p flag on s). This turns SED into a powerful line-extraction tool:
# Without -n: every line printed, matching lines printed twice printf 'a\nb\nc\n' | sed '/b/p' a b b c # With -n: only explicitly printed lines appear printf 'a\nb\nc\n' | sed -n '/b/p' b
Using -n with p for line extraction
# Print only line 7 sed -n '7p' file.txt # Print lines 10 through 20 sed -n '10,20p' file.txt # Print lines matching a pattern sed -n '/^ERROR/p' logfile.txt # Print lines matching either of two patterns (ERE alternation) sed -En '/(ERROR|WARN)/p' logfile.txt # Print a block between two markers (inclusive) sed -n '/BEGIN/,/END/p' file.txt # Print the last line (equivalent to tail -1) sed -n '$p' file.txt # Print every other line (odd lines) sed -n '1~2p' file.txt
Using p intentionally without -n — showing before and after
Sometimes the double-print behaviour of p is deliberate. A classic use is showing what a substitution changed — print the original line with p first, then let the substitution and default print show the modified version:
# Show original and modified side by side for every changed line sed '/foo/ { p; s/foo/bar/; }' file.txt # Lines with "foo": # p prints the original → "this foo line" # s replaces it → "this bar line" # default print outputs it → "this bar line" # Lines without "foo": only appear once (default print)
The p flag on the substitute command
As seen in Chapter 2, the p flag on s prints the pattern space when the substitution succeeds. Combined with -n, this is the cleanest way to see only lines that were actually changed:
# Print only lines where a substitution was made sed -n 's/foo/bar/p' file.txt # Practical: check which config values will change before committing with -i sed -n 's/debug=true/debug=false/p' app.conf
The Line Number Command: =
Though not strictly a print variant, the = command is closely related — it prints the current line number to standard output, followed by a newline. It is worth covering here because it is most useful alongside p:
# Print line numbers for every line (like cat -n) sed '=' file.txt | paste - - # paste merges the number line and content line onto one line # Print line numbers AND lines for lines matching a pattern (like grep -n) sed -n '/ERROR/ { =; p }' logfile.txt 42 2024-01-15 ERROR connection refused 87 2024-01-15 ERROR disk full # Count total lines in a file (print only the last line number) sed -n '$=' file.txt
wc: sed -n '$=' file.txt is a handy alternative to wc -l file.txt. Unlike wc -l, it counts the number of newline-terminated lines without any trailing space or filename in the output — useful in scripts where you need a clean integer.
The Quit Commands: q and Q
The q command tells SED to stop processing after the current line. It prints the pattern space (unless -n is active) and exits. The Q command (GNU sed only) does the same but skips the final print — it exits silently.
Basic quit usage
# Print only the first 5 lines then stop (equivalent to head -5) sed '5q' file.txt # Print up to and including the first line matching a pattern, then stop sed '/ERROR/q' logfile.txt # Print the first line only (equivalent to head -1) sed '1q' file.txt # Extract just the HTTP status line from a curl response curl -sI https://example.com | sed '1q'
Quit with an exit code
GNU sed allows an optional exit code after q and Q. This is useful in shell scripts where the exit status signals a result:
# Exit with code 1 if the pattern is NOT found (check for required config) sed -n '/required_setting/q 0' config.txt; echo $? # Exits 0 if "required_setting" is found, 1 otherwise (default)
The Q command — quit without printing
# Print lines 1 through 4, stop before line 5 (no print for line 5) sed '5Q' file.txt # With q: lines 1,2,3,4,5 are printed (q prints line 5 then quits) # With Q: lines 1,2,3,4 are printed (Q quits before printing line 5)
Nq— prints lines 1 through N, then exits (equivalent tohead -N)NQ— prints lines 1 through N-1, then exits (likehead -Nminus one)
q when you want to include the triggering line in output; use Q when you want to stop just before it.
Performance: Why q and Q Matter on Large Files
SED reads files sequentially line by line. On a 500,000-line log file, if you only need the first match of a pattern, running sed -n '/pattern/p' reads all 500,000 lines even after finding the first match. Adding q stops as soon as the match is found:
# Slow on large files — reads the ENTIRE file even after finding the match sed -n '/ERROR/p' huge.log | head -1 # Fast — stops reading as soon as the first match is found and printed sed -n '/ERROR/ { p; q }' huge.log # Even faster with Q — stops without the final print overhead sed '/ERROR/ { p; Q }' huge.log # (the p runs first, then Q exits — no default print attempted)
q or Q when you only need the first match (or the first N lines) from a large file. The difference in speed can be dramatic — finding a match on line 100 of a million-line file using q is 10,000× faster than processing all million lines.
Combining d, p, and q
The real power emerges when these commands work together. Here are patterns you will use repeatedly:
grep-like filtering with SED
# Print only matching lines — equivalent to: grep "pattern" file sed -n '/pattern/p' file.txt # Print non-matching lines — equivalent to: grep -v "pattern" file sed '/pattern/d' file.txt # Print matching lines with line numbers — equivalent to: grep -n "pattern" file sed -n '/pattern/ { =; p }' file.txt
head and tail equivalents
# First N lines — equivalent to: head -N file sed 'Nq' file.txt # replace N with a number # Last N lines — equivalent to: tail -N file # (requires knowing total line count or using a different approach) lines=$(sed -n '$=' file.txt) sed -n "$((lines - N + 1)),\$p" file.txt # Skip first N lines — equivalent to: tail -n +N+1 file sed '1,Nd' file.txt # replace N with a number
Extracting a specific section
# Print lines 50–60 then stop (efficient — stops at line 60) sed -n '50,60p'; '60q' file.txt # Better: combine the address and quit in one expression sed -n '50{ :loop; p; n; 60q; b loop }' file.txt # Simplest: just use the range address — SED is smart enough sed -n '50,60p' file.txt
Viewing a section of a log file around an error
# Print the first ERROR line and stop sed -n '/ERROR/ { p; q }' app.log # Print everything up to the first ERROR (exclusive) then stop sed '/ERROR/Q' app.log # Print everything from the first ERROR to end of file sed -n '/ERROR/,${p}' app.log
Removing duplicate consecutive lines
# Delete a line if it is identical to the previous one # (uses hold space — covered fully in Chapter 6) sed '$!N; /^\(.*\)\n\1$/!P; D' file.txt # Brief explanation: N appends next line; if the two lines match, D deletes # the first without printing; P prints the first if they differ
Practical Recipes
Extract email addresses from a file
# Print only lines that look like email addresses sed -En '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/p' contacts.txt
View only the active (uncommented) lines of a config
sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' /etc/ssh/sshd_config
Check if a pattern exists and exit appropriately
# Use in a shell script: exit 0 if pattern found, exit 1 if not if sed -n '/required_key/q 0; $q 1' config.txt; then echo "Config OK" else echo "ERROR: required_key missing from config" fi
Print function signatures from a C or shell file
# Print lines that look like shell function declarations sed -n '/^[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*()[[:space:]]*{/p' script.sh # Print C function signatures (very simplified) sed -n '/^[a-z].*([^;]*$/{/^#/d; p}' source.c
Fast search through a large compressed log
# Decompress on-the-fly and stop at first match zcat archive.log.gz | sed -n '/CRITICAL/ { p; q }'
Strip a shebang line from a script
# Remove the first line if it starts with #! sed '1{ /^#!/d }' script.sh
Command Interaction Summary
| Command | Prints pattern space? | Continues script? | Reads next line? | POSIX? |
|---|---|---|---|---|
d |
No | No — aborts rest of script | Yes (next cycle) | Yes |
p |
Yes (immediately) | Yes — script continues | Yes (at end of cycle) | Yes |
= |
No — prints line number | Yes — script continues | Yes (at end of cycle) | Yes |
q |
Yes (unless -n) |
No — exits SED | No — SED terminates | Yes |
Q |
No | No — exits SED | No — SED terminates | GNU only |
Quick Reference — Chapter 4
Delete
-n)
-n: only print explicitly — turns SED into a filter
Quit
head -N)
a (append), i (insert), and c (change) commands — the tools for adding new lines to output and replacing entire lines or blocks. These are essential for tasks like inserting file headers, adding config directives, and replacing stale blocks of content.