Discover how to use sed in Unix effectively to quickly transform text line by line, enhancing your command-line efficiency.
Sed is the Unix stream editor: a command-line tool that transforms text line by line without opening a file or waiting for your input. It reads text, applies a script of editing commands, and writes the result out, all in one non-interactive pass. That behavior is baked into POSIX, the standard that defines how sed must act on any conforming Unix system, while GNU sed layers on extra conveniences most Linux users never think twice about. The tool traces back to Lee E. McMahon at Bell Labs, built between 1973 and 1974 as part of the original Unix toolkit.
Here’s the one line that gets you 80 percent of what people actually use sed for:
sed -i.bak 's/foo/bar/g' notes.txt
That replaces every instance of “foo” with “bar” in notes.txt, edits the file in place, and keeps a backup before it touches anything. Everything else in this guide is really just variations on that pattern: pick some text, describe what you want changed, tell sed where to apply it.
Key Takeaways
Sed transforms text reliably at the line level because it separates matching (addresses), editing (commands like s, d, a), and output control (-n, p) into distinct, composable pieces.
| Point | Details |
|---|---|
| Test before you commit | Run scripts with -n and p first, then switch to -i only once the output looks correct. |
| Back up in-place edits | Always use -i.bak or equivalent, since GNU and BSD/macOS handle -i differently. |
| Know your regex dialect | Use -E or -r for extended regex to avoid backslash-heavy BRE syntax mistakes. |
| Keep complex scripts in files | Use -f script.sed instead of long -e chains for readability and CI review. |
| Match the tool to the task | Choose grep for finding lines, sed for editing them, and awk for field-based reporting. |
What Is Sed in Unix, and When Should You Reach for It?
Sed exists for one job: editing text streams without a human clicking through a document. You feed it a file or piped input, give it a script, and it runs that script against every line, one at a time, then moves to the next. No screen redraw, no cursor, no save dialog.
That non-interactive design is exactly what makes sed useful in places a text editor can’t go: shell scripts, build pipelines, cron jobs, container startup routines. Anywhere a human isn’t sitting at the keyboard, sed can still make edits reliably and repeatably. Wikipedia’s technical summary describes the mechanism plainly: sed loads each line into something called the pattern space, runs your commands against it, and prints the result to standard output unless you’ve told it not to.
You reach for sed when the task is line-oriented: swap a string, delete matching lines, insert a header, renumber something across a hundred files. When the task becomes column-oriented or needs arithmetic, that’s usually a sign you want AWK instead. More on that trade-off later.
Sed Command Syntax and the Flags You’ll Actually Use
The basic shape never changes: sed [options] 'script' file(s). The script goes in quotes so your shell doesn’t try to interpret it, and the file list is optional. Skip it, and sed reads from standard input instead, which is what makes it so useful in pipelines.
You can hand sed a script two ways. The -e flag lets you chain multiple short commands directly on the command line:
sed -e 's/cat/dog/' -e 's/red/blue/' file.txt
The -f flag points to a script file instead, which is the better move once your edits get complicated or need to run the same way across many jobs:
sed -f cleanup.sed file.txt
A handful of flags cover almost everything you’ll do day to day:
-nsuppresses sed’s automatic line-by-line printing, so only lines you explicitly print (with thepcommand) show up in the output.-esupplies one script segment; use it more than once to stack commands.-freads a script from a file rather than the command line.-iedits files in place instead of printing to standard output.-Eor-rswitches sed into extended regular expression mode, which changes how certain regex characters behave.
Quoting matters more than people expect. Single quotes stop the shell from expanding $variables or backticks inside your script, which is what you want almost every time. Double quotes let shell expansion happen first, which occasionally causes a sed script to silently break because a $ got swallowed before sed ever saw it.
Pro Tip: Default to single quotes around every sed script. Only switch to double quotes when you deliberately need the shell to substitute a variable into the pattern.
The -i flag is where GNU and BSD/macOS sed genuinely disagree. On GNU sed, sed -i 's/a/b/' file edits in place with no backup, and sed -i.bak adds one. On BSD and macOS sed, -i requires an argument no matter what, even if that argument is an empty string: sed -i '' 's/a/b/' file. Skip that argument on macOS and sed either throws an error or, worse, treats the next word as a filename and mangles your edit. This trips up more people than any other sed quirk.
| Flag | Purpose | Portability note |
|---|---|---|
-n |
Suppresses automatic printing | Standard on GNU, BSD, and POSIX sed |
-e |
Adds a script segment | Standard everywhere |
-f |
Reads script from a file | Standard everywhere |
-i |
Edits file in place | GNU allows no argument; BSD/macOS requires one, even empty |
-E / -r |
Extended regex mode | GNU supports both; BSD supports -E; POSIX historically lacks either |
Core Sed Commands You’ll Use Every Day
Sed’s commands are mostly single letters, which looks cryptic until you’ve used four or five of them a couple hundred times. The one you’ll type more than all the others combined is s, for substitute:
sed 's/pattern/replacement/flags' file.txt
Add g to replace every match on a line instead of just the first, add p to print the modified line explicitly, or add a number like 2 to replace only the second match per line. Backreferences let you reuse part of the match in the replacement: sed 's/\(.*\)@example.com/\1@newdomain.com/' captures everything before the @ and reinserts it, letting you rewrite email domains without retyping the username.
Beyond substitution, a small set of commands cover nearly every other editing task:
ddeletes the current line and starts the next cycle immediately.pprints the pattern space, most often paired with-nto show only matched lines.aappends text after a matched line;iinserts text before it;cchanges (replaces) the matched line entirely.nandNadvance to the next line, withNappending it to the current pattern space instead of replacing it.=prints the current line number, handy for debugging or generating references.qquits processing immediately, useful for stopping after the first match in a large file.
Commands run in the order sed compiles them, against whichever line is currently loaded into the pattern space. For more elaborate scripts, sed also supports flow control through the b (branch) and t (branch if a substitution succeeded) commands paired with labels, letting you loop or skip sections of a script conditionally.
Sed’s original manual describes the process precisely: commands are compiled before execution, and each one is tied to an address that decides which lines it touches. Understanding that compile-then-apply model is the difference between guessing at sed syntax and actually predicting what a script will do.
How Does Sed Decide Which Lines to Edit?
Every sed command can carry an address, and addresses are what turn sed from a blunt find-and-replace tool into something closer to a scalpel. Without one, a command applies to every line. With one, it applies only where you say.
Line-number addresses are the simplest: 3d deletes line 3, 1s/foo/bar/ edits only the first line, and $d deletes the last line of the file, wherever that happens to be. Ranges extend that idea: 3,5d deletes lines 3 through 5 inclusive.
Regex addresses let you target lines by content instead of position. /error/d deletes every line containing “error.” You can also mix a regex start with a different kind of endpoint, which is one of sed’s more underused tricks: 3,/END/d deletes from line 3 until the first line matching “END,” whatever line number that turns out to be.
A few addressing patterns come up constantly once you start writing real scripts:
- Negate an address with
!to apply a command everywhere except the match:/DEBUG/!dkeeps only lines containing “DEBUG” by deleting everything that doesn’t. - Group multiple commands under one address using braces:
/start/,/end/{s/foo/bar/; s/baz/qux/}applies both substitutions only within that range. - Combine a line number with a regex for asymmetric ranges, like
10,/STOP/p, which starts printing at line 10 and continues until “STOP” appears. - Use
$alone to always mean the last line, regardless of file length, which is handy in scripts that process files of unknown size.
Pattern Space and Hold Space: Sed’s Two Memory Buffers
Every sed command manipulates the pattern space, the buffer holding whatever line (or lines) sed is currently working on. What trips people up is that sed has a second, mostly invisible buffer called hold space, and it persists across cycles even though pattern space gets wiped clean at the start of each new line.
Five commands move text between the two: h copies pattern space into hold space, H appends it instead of overwriting, g copies hold space back into pattern space, G appends it, and x swaps the two outright. On their own those letters mean nothing. Together they let you do things sed otherwise couldn’t, like reversing the order of lines in a file or detecting a pattern on one line and acting on a different line later.
A concrete example: suppose you want to print each line together with the line that came before it. You store the current line in hold space, then on the next cycle, pull the previous line back before processing the new one:

sed -n '1!G;h;$p' file.txt
That one reverses a file entirely (a classic tac replacement), by continually appending each new line to whatever’s already piled up in hold space, then printing the accumulated stack once the last line arrives.
The mechanics come straight from sed’s core read-apply-print loop: pattern space resets every cycle, hold space does not, and that asymmetry is the entire reason multi-line sed tricks work at all. Miss that distinction, and sed’s more advanced examples look like magic instead of logic.
Practical Sed Command Examples You Can Copy Right Now
Most sed work in the real world boils down to a short list of recurring jobs. Here are the ones worth memorizing.
- Replace the first match per line:
sed 's/cat/dog/' file.txt - Replace every match per line:
sed 's/cat/dog/g' file.txt - Reuse matched text with a backreference:
sed 's/\(error\): \(.*\)/\2 (\1)/' log.txtswaps a label and its message around. - Avoid backslash overload with an alternate delimiter:
sed 's#/usr/local/bin#/opt/bin#g' path.txtsidesteps escaping every forward slash in a file path. - Delete lines matching a pattern:
sed '/^#/d' config.txtstrips comment lines starting with#. - Print only matching lines:
sed -n '/warning/p' server.log - Insert a line before a match:
sed '/^\[section\]/i ; new setting here' config.ini - Append text after every match:
sed '/^Total/a Generated automatically' report.txt - Edit safely in place:
sed -i.bak 's/localhost/production.example.com/g' *.conf
That last one is worth pausing on. Running sed -i directly on a batch of config files with no backup is how people accidentally take down a service at 2 a.m. Sed doesn’t ask “are you sure.” It just edits.
Combine sed with other Unix tools and the reach expands considerably. find . -name "*.log" | xargs sed -i 's/DEBUG/INFO/g' walks a directory tree and rewrites every log file it finds. grep -l "TODO" *.py | xargs sed -i '/TODO/d' finds Python files containing “TODO” and strips those lines out entirely.
Pro Tip: Before you ever run sed -i on real data, run the identical script with -n and a trailing p instead. If the printed output looks right, swap -n ... p for -i with confidence instead of hope.
BRE vs ERE: Why Your Regex Isn’t Matching
Sed supports two regex dialects, and mixing them up is probably the single most common reason a sed script fails silently or behaves in a way that makes no sense. Basic Regular Expressions (BRE) are the default. Extended Regular Expressions (ERE) unlock with the -E or -r flag.
In BRE, characters like +, ?, and {} are treated as literal text unless you escape them with a backslash: \+, \?, \{2,4\}. In ERE, those same characters work as metacharacters without any escaping needed, which is usually closer to what people expect coming from other regex-flavored tools like grep -E or Perl.
Course materials on BRE and ERE differences point to this exact confusion as the top beginner stumbling block: writing sed 's/ab+/X/' and expecting it to match “ab” one or more times, when in BRE that pattern actually matches the literal string “ab+.”
- BRE requires backslashes for
+,?,{n,m}, and grouping parentheses:\(,\). - ERE (via
-Eor-r) treats those same symbols as active regex operators with no escaping required. - Alternative delimiters (
|,#,@) reduce escaping headaches when your pattern or replacement contains forward slashes, which is common when editing file paths or URLs. - GNU sed accepts both
-Eand-ras synonyms for extended mode; older or stricter POSIX-conformant sed implementations may not recognize either flag at all.
Sed’s design intentionally kept BRE as the default for backward compatibility with decades-old scripts. That decision is also exactly why so many modern regex habits break the first time someone runs a script on a strict POSIX or older BSD sed instead of GNU sed.
Platform differences don’t stop at regex dialect. GNU sed adds convenience features (certain escape sequences, -z for null-delimited records, in-place editing without a backup argument) that aren’t guaranteed on every Unix variant. A script that works flawlessly on a Linux CI runner can fail outright on a macOS developer’s laptop, purely because of -i argument handling or an unsupported escape.
Building Reusable Sed Scripts and Pipelines
Once a sed command grows past two or three clauses, cramming it all into one -e string starts working against you. Storing the script in a file with -f keeps the logic readable, versionable, and free from shell-escaping headaches:
sed -f rename-fields.sed data.csv
That script file can hold comments, blank lines for readability, and multiple commands, one per line, without any of the quoting gymnastics a single-line -e chain would demand.
Sed slots naturally into pipelines as a filter between other tools. A common pattern extracts data with grep, reshapes it with sed, then sorts or counts the result:
grep "ERROR" app.log | sed 's/^\[.*\] //' | sort | uniq -c
That pulls every error line, strips a leading timestamp bracket, then counts how many times each unique message occurred. In continuous integration jobs, script files also make audits easier: reviewers can read a .sed file in a pull request instead of parsing an inline shell one-liner buried in a build script.
Debugging a misbehaving sed script rarely needs anything more elaborate than two tricks. First, run with -n and add p to see exactly what’s matching before you commit to an edit. Second, insert = at strategic points in your script to print line numbers, confirming which lines sed is actually touching. Always test against a small sample file copied from production data rather than the full dataset. A five-line test file surfaces a broken regex just as fast as a five-million-line one, without the risk.
Common Sed Mistakes and How to Fix Them
A handful of errors account for most of the frustration people report when they’re starting out with sed.
- Shell quoting failures. Wrapping a script in double quotes lets the shell expand
$1or`date`before sed ever sees the script, often silently breaking it. Default to single quotes unless you specifically need shell substitution. - Delimiter collisions. Using
/as the delimiter when your pattern or replacement already contains a slash forces you to escape every one of them. Switch delimiters instead:s#/path/one#/path/two#reads far more cleanly than the escaped version. - Assuming
gisn’t needed. Forgetting thegflag means only the first match on each line gets replaced, which looks like sed “half worked” when really it did exactly what you asked. - Running
-iuntested. In-place edits with no backup and no prior dry run are how a bad regex destroys a file with zero chance of recovery. - Ignoring implementation differences. A script tested only on GNU sed can fail on BSD/macOS sed over
-iargument requirements or missing-Esupport.
A short troubleshooting checklist catches nearly all of these before they cause damage: run the script with -n and p first, test on a small sample file, always keep an -i.bak backup on first use, and confirm which sed implementation (sed --version on GNU systems) you’re actually running before assuming GNU-only behavior is available.
Sed vs Grep vs Awk: Picking the Right Tool
These three tools get grouped together constantly, and for good reason: they solve adjacent but distinct problems. Grep finds lines. Sed edits lines. AWK processes fields within lines and produces structured reports. A direct comparison of the three frames it exactly that way: grep for matching, sed for line-oriented transformation, awk for column-aware data work.
Need to know which lines in a log file contain “timeout”? That’s grep: grep "timeout" app.log. Need to replace every occurrence of “timeout” with “connection_timeout” across the whole file? That’s sed: sed 's/timeout/connection_timeout/g' app.log. Need to sum the third column of a CSV file where the second column equals “active”? That’s a job for awk, not sed, because awk understands fields and arithmetic in a way sed simply doesn’t: awk -F, '$2=="active" {sum+=$3} END {print sum}' data.csv.
- Sed excels at search-and-replace, deleting matched lines, and lightweight in-pipeline reformatting.
- AWK is the better fit once you need column math, conditional field logic, or formatted multi-column reports.
- Grep alone is enough when you only need to know whether or find where a pattern occurs, with no editing involved.
- The three tools chain together constantly in real pipelines: grep to filter, sed to reshape, awk to summarize.
Sed’s cousin relationship to AWK isn’t accidental. Both grew out of the same Unix-era philosophy of small, composable tools, and learning both together covers the overwhelming majority of everyday text-processing needs without reaching for a full scripting language.
Running Sed Safely in Production and CI Pipelines
Sed’s footprint is tiny. It reads and processes text a line at a time, without loading entire files into memory the way some scripting languages do by default, which makes it fast and dependable for line-oriented jobs even on modest hardware. For heavier data-parsing work involving complex field logic or large-scale aggregation, AWK or a full language like Perl or Python will usually outperform a sprawling sed script, if only because those tools were designed for structured data rather than raw text streams.
A few operational habits separate a sed script that works reliably in production from one that eventually causes an incident:
- Always test with
-nand a printed sample before switching to-i, and never skip the backup suffix on a first production run. - Store scripts in version control and prefer
-f script.sedover long inline-echains in CI configuration, so reviewers can actually read the diff. - Design edits to be idempotent. A script that runs twice on the same file should produce the same result as running it once, which matters enormously in retry-happy CI systems.
- Log what changed, even minimally, so a bad deploy can be traced back to the exact transformation that caused it.
Teams managing bulk content edits across a CMS run into these same idempotence and backup concerns; the patterns for avoiding content management mistakes at scale apply just as directly to sed-driven batch edits as they do to manual CMS workflows. The same discipline shows up in automation-heavy environments generally, where pipeline reliability depends on scripts behaving the same way every single time they run, not just the first time someone tested them.
Pro Tip: If you’re using sed inside a container’s startup script to rewrite config values at boot, always run it against a copy of the file first, or bake in a check that skips the edit if it’s already been applied. Otherwise a container restart can silently reapply the same substitution twice, corrupting a value that was only supposed to change once.
Why Sed Still Earns Its Place in 2026
Sed’s staying power isn’t nostalgia. It’s dependency weight. A sed script needs nothing beyond a POSIX-compliant shell and the binary itself, which ships by default on virtually every Linux distribution and macOS install. Compare that to a Python or Node script for the same one-line substitution: an interpreter, possibly a package manager, possibly a virtual environment. In a minimal container image or a boot-time configuration step, that difference between “sed is already there” and “now we need to install a runtime” decides build times and image sizes.
The honest case for sed isn’t that it replaces AWK, Perl, or Python for serious data work. It doesn’t, and treating a fifteen-line sed script as more maintainable than an equivalent ten-line Python script is usually a mistake. The case for sed is narrower and more useful: for deterministic, line-oriented, single-purpose transforms, especially ones that need to run inside something small and dependency-averse, sed remains the most direct tool available. Its original design goal was exactly that: a small domain-specific filter for text streams, and that scope is precisely why it hasn’t needed to change much in fifty years.
At Rule27design, sed has earned a permanent spot in deployment tooling for exactly this reason. Configuration edits during deploys need to be predictable every single time, with no interpreter startup cost and no surprise dependency drift between environments, and a well-tested sed one-liner delivers that more reliably than heavier alternatives built for jobs sed was never asked to do.
Where to Read More About Sed
The sed(1) Linux manual page remains the definitive reference for command syntax and standard options on Linux systems, and it’s the first place to check when a flag behaves unexpectedly. The original sed manual documents the addressing model, pattern and hold space, and flow-control commands in more depth than most tutorials attempt. For a broader history and technical overview, Wikipedia’s entry on sed covers the tool’s origins and its core read-apply-print loop clearly.
For the BRE versus ERE distinction specifically, the University of Michigan course notes on sed walk through the regex dialect differences that trip up most newcomers. A GeeksforGeeks reference on sed commands offers a solid library of runnable examples worth bookmarking for quick lookups. And a short comparison of grep, sed, and awk helps clarify which tool fits which job once you’ve got the sed basics down.
Frequently Asked Questions
What does sed stand for, and what is it used for in Unix? Sed stands for “stream editor.” It’s used for non-interactive, line-by-line text transformations: substituting text, deleting lines, inserting or appending content, and filtering output within scripts and pipelines.
What’s the difference between sed and awk? Sed operates line by line, matching patterns and applying edits like substitution or deletion. Awk operates on fields within each line, making it the stronger choice for column-based math, conditional field logic, and formatted reports.
Why does sed -i behave differently on macOS than on Linux?
GNU sed, standard on Linux, allows -i with no argument for backup-free in-place edits. BSD sed, which ships with macOS, requires an explicit argument after -i, even an empty string (-i ''), or the command either errors out or misinterprets the next argument as a filename.
How do I use extended regular expressions in sed?
Add the -E flag (or -r on GNU sed) to your command. That switches sed into Extended Regular Expression mode, so characters like +, ?, and {} work as active regex operators without needing a backslash in front of them.
Is sed still relevant compared to modern scripting languages like Python? Yes, for line-oriented, single-purpose edits, especially inside shell scripts, build pipelines, or minimal containers where installing a full language runtime isn’t practical. For complex data parsing or heavy logic, Python, Perl, or AWK are usually the better fit.
Sources
About the Author
Josh AndersonCo-Founder & CEO at Rule27 Design
Operations leader and full-stack developer with 15 years of experience disrupting traditional business models. I don't just strategize, I build. From architecting operational transformations to coding the platforms that enable them, I deliver end-to-end solutions that drive real impact. My rare combination of technical expertise and strategic vision allows me to identify inefficiencies, design streamlined processes, and personally develop the technology that brings innovation to life.
View Profile


