The Portability Landscape
There is no single sed: the two dominant implementations are GNU sed (Linux) and BSD sed (macOS and the BSDs), with POSIX defining the common baseline. GNU sed adds many conveniences — the -E/-r ERE flags, \+ and \? in BRE, in-place -i without a suffix, and escape sequences like \n and \t in replacements — that BSD sed either handles differently or not at all. Writing portable sed means sticking to the POSIX-guaranteed feature set or detecting the implementation at runtime, which matters greatly for scripts shipped to heterogeneous machines.
Cricket analogy: It's like the same game played under different boards' playing conditions — a Test at Lord's and one at the MCG follow the ICC baseline (POSIX) but each ground adds local rules (GNU/BSD extensions).
Concrete GNU vs BSD Divergences
Several differences bite in practice. In-place editing differs: GNU 'sed -i' takes an optional suffix attached, while BSD 'sed -i' requires a separate suffix argument (use -i '' for none). Escape sequences in the replacement — \n, \t — are interpreted by GNU sed but often emitted literally by BSD sed, so inserting a newline portably requires a literal backslash-newline in the script. The a, i, and c commands (append, insert, change) have strict POSIX syntax requiring a backslash and newline, which GNU relaxes to a one-line form. Word boundaries (\b, \<, \>) and case-conversion (\U, \L, \E) are GNU extensions absent from BSD sed entirely.
Cricket analogy: It's like a no-ball rule enforced strictly in one series and leniently in another — the append command's syntax is rigid under POSIX/BSD but GNU relaxes the front-foot line.
# Detect the implementation and branch
if sed --version 2>/dev/null | grep -q GNU; then
SED_INPLACE=(-i) # GNU
else
SED_INPLACE=(-i '') # BSD/macOS
fi
sed "${SED_INPLACE[@]}" 's/^debug=.*/debug=false/' app.conf
# Portable multi-line append (POSIX a\ form works on GNU and BSD)
sed '/\[server\]/a\
max_connections = 200' server.ini
# Fast path: quit early once you have what you need (huge files)
sed -n '1,100p;100q' huge.log # print first 100 lines, then stop reading
# Prefer a single sed with multiple -e over chaining many sed processes
sed -e 's/foo/bar/' -e '/^#/d' -e 's/ */ /g' data.txtThe 'q' (quit) command is a major optimization on large inputs. 'sed 100q file' stops after line 100 instead of streaming the whole file, and 'sed -n "/PATTERN/{p;q}"' prints the first match and exits immediately. Quitting early can turn a multi-second scan of a gigabyte log into a millisecond operation.
Performance Techniques
sed is already fast because it streams, but you can do better. Combine multiple edits into one sed invocation with several -e expressions rather than piping through several sed processes — each process has fork and startup cost. Anchor regexes and avoid unnecessary backtracking; a leading '^' lets sed reject non-matching lines quickly. Use address restrictions so a substitution only runs on relevant lines, e.g. '/error/s/foo/bar/', which skips the substitution engine on lines that can't match. Finally, be aware that setting the C locale (LC_ALL=C) can dramatically speed up sed on ASCII data by avoiding multibyte UTF-8 processing, sometimes by an order of magnitude.
Cricket analogy: Combining edits into one sed pass is like a fielder completing a run-out with a single throw instead of relaying through three players — fewer handoffs (processes) means less lost time.
Setting LC_ALL=C for speed changes character semantics: byte ranges like [a-z] and case handling assume single-byte ASCII. If your data contains UTF-8 multibyte characters (accents, emoji, non-Latin scripts), the C locale can mangle them or match incorrectly. Only use LC_ALL=C when you know the input is plain ASCII, or you may silently corrupt text.
- There is no single sed: GNU, BSD/macOS, and the POSIX baseline diverge in real ways.
- Portable scripts stick to POSIX features or detect the implementation with 'sed --version'.
- Key divergences: -i suffix handling, literal vs interpreted \n/\t, and a/i/c command syntax.
- \b word boundaries and \U/\L case conversion are GNU-only extensions, absent in BSD sed.
- Combine edits with multiple -e in one invocation to avoid per-process fork overhead.
- Use 'q' to quit early and address restrictions to skip irrelevant lines on large files.
- LC_ALL=C speeds up ASCII processing but can corrupt UTF-8 data, so use it only on plain ASCII.
Practice what you learned
1. Which feature is a GNU sed extension not available in BSD sed?
2. How does 'sed 100q file' improve performance on a huge file?
3. Why prefer 'sed -e s/a/b/ -e /^#/d file' over piping two sed processes?
4. What is the risk of setting LC_ALL=C to speed up sed?
5. What is the portable difference in the in-place flag between GNU and BSD sed?
Was this page helpful?
You May Also Like
Config File Editing with sed
Using sed to safely automate edits to configuration files — in-place editing with backups, targeting keys precisely, and pitfalls when scripting changes to production configs.
sed in Shell Pipelines
How to wire sed into Unix pipelines as a stream filter, combining it with cat, grep, sort, and xargs to build powerful one-liner text-processing workflows.
sed vs awk vs grep
A comparison of the three classic Unix text tools — when to reach for grep to search, sed to edit streams, and awk for field-oriented and programmable processing.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics