If you’ve spent any time on the Linux command line, you’ve probably seen the sed command used in a one-liner and thought “I should learn that properly.” Most people pick it up piecemeal, learning just enough to substitute text and move on. That’s leaving a lot on the table.
sed stands for stream editor. It reads input line by line, applies your instructions, and writes the result to standard output. No interactive interface, no temp files needed. It’s fast, scriptable, and available on every Linux system you’ll ever touch.
This guide covers the sed commands and patterns I actually use day to day, from simple substitutions to multi-expression edits and in-place file changes. If you work with config files, logs, or text pipelines, this is the reference you’ll keep coming back to.
In This Article
How sed Processes Text

Before diving into commands, it helps to understand what sed is actually doing under the hood. When sed reads a file, it works with two internal buffers:
- Pattern space: the active buffer where
sedplaces the current line and applies your commands. It gets cleared after each line is processed (unless you tell it otherwise). - Hold space: a secondary buffer for temporary storage. Data can be moved between the pattern space and hold space using
h,H,g,G, andxcommands. It persists across lines.
For most day-to-day work, you’ll never touch the hold space directly. But knowing it exists explains how sed can handle multi-line operations and why certain advanced recipes work the way they do. The GNU sed manual covers the execution cycle in full detail.
Basic Syntax
The general form is:
sed [options] 'command' file
Or piped from another command:
some-command | sed 'command'
You can also read commands from a script file using -f, which is useful when your edits get complex enough to be worth saving.
Substitution: The Command You’ll Use Most
The s command is the workhorse of sed. The syntax is:
sed 's/pattern/replacement/' file
By default it replaces only the first match on each line. To replace all matches on every line, add the g flag:
sed 's/foo/bar/g' file.txt
Case-insensitive matching uses the I flag (GNU sed):
sed 's/error/WARNING/gI' app.log
You can also target only a specific occurrence. This replaces only the second match on each line:
sed 's/foo/bar/2' file.txt
Note: The delimiter doesn’t have to be /. If your pattern contains slashes, use any other character. This is handy for file paths:
sed 's|/old/path|/new/path|g' config.txt
Editing Files In-Place
By default sed writes to stdout and leaves the original file untouched. To edit the file directly, use -i:
sed -i 's/ServerName old.example.com/ServerName new.example.com/' /etc/apache2/apache2.conf
Always back up first when editing config files. The -i flag accepts an optional suffix to create a backup automatically:
sed -i.bak 's/listen 80/listen 8080/' /etc/nginx/nginx.conf
That leaves nginx.conf.bak as your safety net. I use this constantly when scripting config changes across multiple servers.
Restricting Changes to Specific Lines
You can tell sed to operate only on certain lines by adding an address before the command.
By line number:
sed '3s/foo/bar/' file.txt
A range of lines:
sed '5,10s/foo/bar/g' file.txt
From a line to the end of file:
sed '5,$s/foo/bar/g' file.txt
Only lines matching a pattern:
sed '/^#/s/old/new/g' file.txt
That last one is useful. It restricts substitution to lines starting with #, so you can safely modify comments without touching live config values.
You can also negate an address with !. This applies the substitution to every line that does NOT start with #:
sed '/^#/!s/old/new/g' file.txt
Deleting Lines
The d command deletes matching lines from the output.
Delete blank lines:
sed '/^$/d' file.txt
Delete lines containing a pattern:
sed '/^#/d' file.txt
That strips all comment lines. Combined with blank-line deletion, it’s a quick way to see just the active directives in a config file:
sed '/^#/d;/^$/d' /etc/ssh/sshd_config
Delete a specific line by number:
sed '5d' file.txt
Delete a range:
sed '10,20d' file.txt
Printing Lines: p and -n
By default sed prints every line. The -n flag suppresses that, and the p command prints explicitly. Together they let you extract matching lines, similar to grep but with the full power of sed expressions:
sed -n '/ERROR/p' app.log
Print lines in a range:
sed -n '10,20p' file.txt
Print lines between two patterns:
sed -n '/START/,/END/p' file.txt
That last pattern is something grep alone can’t do. It prints everything between and including the matching boundary lines.
Inserting, Appending, and Changing Lines
Three commands handle structural edits to a file:
iinserts text before a matched lineaappends text after a matched linecreplaces a matched line entirely
Insert a comment before a specific line:
sed '5i # Added by deploy script' file.txt
Append a line after every line matching a pattern:
sed '/^PermitRootLogin/a # Change requires sshd restart' /etc/ssh/sshd_config
Replace an entire line:
sed '/^PermitRootLogin.*/c PermitRootLogin no' /etc/ssh/sshd_config
That last one is cleaner than a substitution when you want to enforce an exact value regardless of what’s currently on that line.
Multiple Expressions
You can chain multiple sed commands in one pass using -e:
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
Or separate them with semicolons inside a single expression:
sed 's/foo/bar/g;s/baz/qux/g' file.txt
Running multiple substitutions in a single sed pass is faster than piping through multiple sed calls, which matters when processing large log files.
Using Capture Groups
sed supports backreferences in substitutions. Wrap the part you want to capture in ( and ), then reference it as 1, 2, etc.
Swap the order of two words separated by a colon:
echo "hello:world" | sed 's/(.*):(.*)/2:1/'
Output:
world:hello
A practical example: extract the IP address from a log line:
echo "client connected from 192.168.1.50 port 22" | sed 's/.*from ([0-9.]+).*/1/'
Output:
192.168.1.50
If you prefer extended regex (fewer backslashes, cleaner syntax), use -E:
echo "client connected from 192.168.1.50 port 22" | sed -E 's/.*from ([0-9.]+).*/1/'
Transliterate with y
The y command works like tr: it replaces characters one-to-one. Unlike s, it doesn’t use regex. It simply maps each character in the first set to the corresponding character in the second set.
Convert lowercase to uppercase:
echo "hello" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'
Replace tabs with spaces:
sed 'y/t/ /' file.txt
It’s niche, but when you need a straight character swap without regex overhead, y is the right tool.
Writing Matches to a File
The w command writes matching lines to a separate file. You can use it standalone or as a flag on the s command.
Write all lines containing “ERROR” to a separate file:
sed -n '/ERROR/w errors.log' app.log
Write only lines where a substitution was made:
sed -n 's/foo/bar/gw changes.log' file.txt
This is useful for splitting a file based on content or creating an audit trail of what was changed during a batch edit.
Labels and Branching
For advanced use cases, sed supports labels (:), unconditional branching (b), and conditional branching (t). These let you build loops and basic conditionals.
Add commas to a large number (recursive substitution):
echo "1234567890" | sed -E ':loop; s/([0-9])([0-9]{3})(,|$)/1,23/; t loop'
Output:
1,234,567,890
The :loop label marks a point, t loop branches back to it if the previous s command made a substitution, and the loop stops when no more substitutions are possible.
Join lines ending with a backslash (common in Makefiles and shell scripts):
sed ':a; /$/ { N; s/n//; ta }' file.txt
Branching is where sed starts to feel more like a programming language than a text editor. For anything beyond a simple loop, you’re probably better off reaching for awk or Perl. But for recursive patterns like the comma insertion above, it’s worth knowing.
Real-World Examples
These are patterns I come back to regularly.
Comment out a config directive
sed -i 's/^PasswordAuthentication yes/# PasswordAuthentication yes/' /etc/ssh/sshd_config
Uncomment a line
sed -i 's/^# (PermitRootLogin)/1/' /etc/ssh/sshd_config
Strip trailing whitespace from every line
sed -i 's/[[:space:]]*$//' file.txt
Add a line to the end of a file
sed -i '$a net.ipv4.ip_forward=1' /etc/sysctl.conf
Remove ANSI color codes from log output
sed 's/x1b[[0-9;]*m//g' colored.log
If you’re working with journalctl output or any tool that emits colored text, piping through this sed command gives you clean, parseable output.
Replace a value in a key=value config file
sed -i 's/^(max_connectionss*=s*).*/1500/' /etc/mysql/my.cnf
This is better than a plain substitution because it matches the key regardless of its current value, and preserves any surrounding whitespace.
Extract lines between two timestamps in a log
sed -n '/2025-11-18 14:00/,/2025-11-18 15:00/p' /var/log/app.log
Useful when you’re chasing down what happened during a specific window, like the Cloudflare outage on Nov 18, 2025, or any incident with timestamped logs.
Bulk rename file extensions in a directory
for f in *.txt; do mv "$f" "$(echo "$f" | sed 's/.txt$/.md/')"; done
A quick way to convert a batch of files. You can adapt this pattern for any renaming logic that sed can express.
sed vs awk: When to Use Which
Both tools process text line by line, and there’s overlap. The simple rule I follow: use sed for substitutions, deletions, and line-level edits. Use awk when you need to work with fields, do arithmetic, or write logic with conditionals. If you’re wrestling with structured columnar data, awk wins. If you’re editing config files or transforming text streams, sed is cleaner and faster to write.
Both are worth knowing well. They complement each other and both show up constantly in scripts written by experienced sysadmins. See the 90+ Linux commands frequently used by sysadmins for a broader look at the toolkit.
Useful Options Reference
-n: Suppress automatic printing of lines-i: Edit file in-place (add a suffix for backup:-i.bak)-e: Add an expression (can be used multiple times)-f: Read commands from a script file-Eor-r: Use extended regular expressions--debug: Print the parsed script and annotate execution (GNU sed 4.6+, useful for understanding complex scripts)
A Note on Portability
GNU sed (the version on Linux) and BSD sed (macOS) differ in a few ways. The most common gotcha: -i on macOS requires a suffix argument, even if it’s empty:
# macOS / BSD sed sed -i '' 's/foo/bar/g' file.txt # GNU sed (Linux) sed -i 's/foo/bar/g' file.txt
If you’re writing scripts that need to run on both, account for this. The Wikipedia entry on sed has a good summary of the differences between implementations. On Linux servers you’ll rarely hit this issue, but it bites people writing cross-platform automation.
Putting It All Together
Here’s a practical script that sanitizes a config template before deployment. It removes comment lines, strips blank lines, sets a hostname, and enables a specific directive:
sed -e '/^#/d'
-e '/^$/d'
-e "s/^hostname=.*/hostname=$(hostname)/"
-e 's/^# (log_level=info)/1/'
app.conf.template > app.conf
Four operations, one pass, no temp files. That’s sed working the way it’s designed to. When you’re managing config rollouts across a fleet of servers, patterns like this save a lot of time. Pair it with SCP or rsync to distribute the result and you have a lightweight deployment pipeline without needing any extra tooling.
For anything more complex, such as branching logic or multi-file transforms, look at automation tools like Ansible or a proper templating engine. But for quick, targeted, reliable text edits on Linux, the sed command is hard to beat. It’s been doing this job since 1974 and it’s not going anywhere.
Also see: Boost Your Linux Command Line Productivity, Part 1 and Linux File Permissions Explained for more tips on working faster in the terminal.






