30.3 sed: Stream Editor for In-Place Substitutions and Deletions
Right, let’s talk about sed. If grep is your search tool, sed is your text-wrangling scalpel. The name stands for “stream editor,” which sounds boringly technical, but its real power is performing automatic, programmatic edits on text, either from a file or piped from another command. Most people meet sed for one reason: to replace text. And we’ll start there, because honestly, that’s what it does 90% of the time.
The Absolute Basics: Substitution
The s command is your bread and butter. The syntax is s/pattern/replacement/flags. It looks for a pattern (a regular expression, just like in grep) and replaces it with your replacement text.
# Let's say you have a file, config.txt, with a line: DB_HOST=localhost
# You want to change it to point to your production database.
sed 's/localhost/prod-db.myapp.com/' config.txt
Run that. You’ll see the changed line printed to your screen. But notice: the original file, config.txt, is untouched. sed by default is a read-only operator; it streams the edited text to stdout. This is a safety feature. It lets you preview your changes before you do something terrifyingly permanent.
To make that change permanent, you tell sed to edit the file in-place. You use the -i flag. On macOS and BSD systems, you must provide a backup extension (e.g., -i '.bak'). On Linux, it’s usually optional. I always provide one because I’m not a maniac.
# This changes config.txt and creates a backup config.txt.bak
sed -i '.bak' 's/localhost/prod-db.myapp.com/' config.txt
# On most Linux systems, you can skip the backup (DANGER ZONE)
sed -i 's/localhost/prod-db.myapp.com/' config.txt # No backup. Live dangerously.
Flags: Getting Fancy with Your Replacing
That s command can take flags. The most common one is g, which stands for “global.” Without it, sed only replaces the first occurrence on each line. This trips up everyone at least once.
echo "hello hello world" | sed 's/hello/goodbye/'
# Output: goodbye hello world
echo "hello hello world" | sed 's/hello/goodbye/g'
# Output: goodbye goodbye world
Other useful flags are a number (e.g., s/hello/goodbye/2) to replace only the nth occurrence on a line, or p to print the line if a substitution was made (often used with -n to suppress automatic output).
It’s Regular Expressions, Obviously
The pattern is a regex. This is where sed moves from handy to powerful. You’re not just replacing literal words.
# Remove all leading whitespace from a file
sed 's/^[[:space:]]*//' myfile.txt
# Swap the first two words in a line
sed 's/\([^ ]*\) \([^ ]*\)/\2 \1/' myfile.txt
Hold on, what’s with the backslashes and parentheses? Welcome to one of sed’s rough edges: by default, it uses Basic Regular Expressions (BREs). In BREs, the capture parentheses ( and ) and alternation | need to be escaped with a backslash to work as metacharacters. It’s a historical quirk that feels downright user-hostile. This is why many people (including me) almost always use -E (or -r on some older systems) to enable Extended Regular Expressions (EREs), which behave more sensibly.
# The sane way with -E
sed -E 's/([^ ]*) ([^ ]*)/\2 \1/' myfile.txt
Beyond Substitution: Deletion and More
sed isn’t a one-trick pony. The d command deletes lines. This is essentially grep -v on steroids.
# Delete every line containing "DEBUG"
sed '/DEBUG/d' application.log
# Delete lines 10 through 20 of a file
sed '10,20d' myfile.txt
You can also use a for appending text, i for inserting, and c for changing a line. Their syntax is a bit clunky, requiring backslashes unless you use a separate script file.
# Append a new line after every line containing "FIXME"
sed '/FIXME/a\ --- This needs attention ---' notes.txt
The Golden Rule: Know Your Delimiter
You don’t have to use / as your delimiter. This is a godsend when your pattern or replacement contains slashes. Trying to edit a file path is the classic nightmare:
# This is a syntax error because of all the extra slashes
sed 's/etc\/nginx/usr\/local\/nginx/' bad_example.txt
# This is clean and readable. I use '#' almost every time I work with paths.
sed 's#/etc/nginx#/usr/local/nginx#' good_example.txt
You can use almost any character as a delimiter: |, :, ,. Just pick one that isn’t in your search or replace pattern.
In-Place Editing: A Word of Caution
I cannot stress this enough: always test your sed command without -i first. Pipe it to less or redirect it to a test file. sed will happily perform your command, save the result, and destroy your original file without a second thought. If your regex is slightly wrong, you can end up with a corrupted file and no backup. I’ve done it. You will do it. Using -i '.bak' is your get-out-of-jail-free card. Use it. Your future self, frantically trying to restore a config file from a backup at 3 AM, will thank you.