sed is the tool you use when you do not want to open an editor. You have 50 configuration files to update, a password to mask, a hostname to switch from prod to staging across every file of a directory: sed does it in one line, in a scriptable and repeatable way.
It directly serves the RHCSA objective "Create and edit text files", and extends "Use grep and regular expressions to analyze text". The current LFCS curriculum does not name it, but its performance-based tasks constantly ask for this kind of configuration change.
What you will learn
- Substitute text with the
s/before/after/command and its variants - Edit a file directly on disk with
sed -iwithout losing the original - Target specific lines with numeric addresses or regex patterns
- Delete lines, comments and empty lines
- Chain several transformations in a single command with
-e - Reorder fields with
\1,\2capture groups - Apply a complete config file migration workflow
In which context?
sed is the default tool as soon as you must transform text in an automatable, non-interactive way:
- change the database URL in
db.confduring a prod → staging migration - mask a password or an API key in config files before committing them
- remove comments and empty lines from a configuration file to feed it to a parser
- normalise the spaces around
=in dozens of.inior.conffiles - extract only the error lines of a log to feed a report
In a deployment script, sed -i is often the first solution: simple, dependency-free, available on every Linux system. In an Ansible playbook, prefer the lineinfile and replace modules, which only modify the file when needed and report it: a sed -i run by Ansible rewrites the file on every run, without saying whether it changed anything.
The three datasets of this guide
Every output on this page was measured with GNU sed 4.9 on Debian 13, from the three files below. Create them in a working directory to reproduce each command. hosts.old mimics a production /etc/hosts file, db.conf a configuration with irregular spacing, and access.log an excerpt of a web log.
cat > hosts.old <<'EOF'192.168.1.10 web01.prod.example.com web01192.168.1.11 web02.prod.example.com web02192.168.1.20 db01.prod.example.com db0110.0.0.5 bastion.prod.example.com bastionEOF
cat > db.conf <<'EOF'# Database configurationhost = db.internal.example.comport = 5432user=adminpassword= secret123database=productionmax_connections = 100# timeout in secondsconnect_timeout= 30EOF
cat > access.log <<'EOF'192.168.1.10 - alice [01/Apr/2026:08:15:02 +0000] "GET /index.html HTTP/1.1" 200 104310.0.0.5 - bob [01/Apr/2026:08:16:44 +0000] "POST /login HTTP/1.1" 401 219192.168.1.10 - alice [01/Apr/2026:08:17:30 +0000] "PUT /api/orders/7 HTTP/1.1" 500 0EOFThe s command: substitute text
The s command is the heart of sed. Its syntax:
s/PATTERN/REPLACEMENT/FLAGS- PATTERN: regular expression (BRE by default, ERE with
-E) - REPLACEMENT: the replacement text
- FLAGS:
g(global),i(case-insensitive),2(second occurrence only)
Basic substitution, first occurrence per line
The minimal form replaces prod with staging in hosts.old and sends the result to standard output, without touching the file. Each line contains only one prod: the substitution therefore changes all of them.
sed 's/prod/staging/' hosts.old192.168.1.10 web01.staging.example.com web01192.168.1.11 web02.staging.example.com web02192.168.1.20 db01.staging.example.com db0110.0.0.5 bastion.staging.example.com bastionWithout a flag, sed replaces only the first occurrence on each line, then moves to the next line. In a file where the word appears twice per line, the second occurrence stays untouched.
The /g flag, every occurrence
The g (global) flag applies the substitution to every occurrence of each line. The shortest demonstration uses echo, with the same word three times on a single line.
echo "prod prod prod" | sed 's/prod/staging/g'staging staging stagingEdit in place with sed -i
Without -i, sed sends the result to standard output, and the original file stays intact: it is a filter, like grep. With -i (in-place), the change is written directly into the file. The right reflex: run the command without -i, read the output, then run it again with -i.
cp hosts.old hosts.stagingsed -i 's/prod/staging/g' hosts.stagingCreate an automatic backup with -i.bak
The extension passed right after -i becomes the suffix of the backup file. On GNU sed, it sticks to the option (-i.bak), without a space:
sed -i.bak 's/prod/staging/g' hosts.stagingls -1# hosts.staging ← modified file# hosts.staging.bak ← copy of the originalAddresses: target specific lines
By default, sed applies the command to every line. An address limits the action to a selection.
Numeric addresses
A numeric address designates a line by its number, a range by two numbers separated by a comma, and the last line by $. This $ has nothing to do with the end-of-line anchor of regex: its position, before the command, gives it this meaning.
# Edit only line 2sed '2s/example/EXAMPLE/' hosts.old
# Edit lines 1 to 2sed '1,2s/example/EXAMPLE/' hosts.old
# Edit the last linesed '$s/example/EXAMPLE/' hosts.oldThe last command changes only the bastion line:
192.168.1.10 web01.prod.example.com web01192.168.1.11 web02.prod.example.com web02192.168.1.20 db01.prod.example.com db0110.0.0.5 bastion.prod.EXAMPLE.com bastionRegex addresses
A regex address, written between slashes, selects the lines that contain the pattern. It is the most useful form in administration: you target a group of servers by name rather than by a line number that changes at the slightest addition.
# Edit only the lines that contain "web"sed '/web/s/prod/staging/' hosts.old192.168.1.10 web01.staging.example.com web01192.168.1.11 web02.staging.example.com web02192.168.1.20 db01.prod.example.com db0110.0.0.5 bastion.prod.example.com bastionThe db01 and bastion lines do not match the web pattern: they come out unchanged, with their prod. Without an address, the same substitution would have changed everything.
Delete lines with d
The d (delete) command removes from the output the lines selected by the address. The ^ anchor matters: without it, /#/d would also delete a valid line followed by an end-of-line comment.
# Delete the comments and empty lines of a config filesed '/^#/d; /^$/d' db.confhost = db.internal.example.comport = 5432user=adminpassword= secret123database=productionmax_connections = 100connect_timeout= 30The comments (# ...) are gone, and any empty line would have met the same fate. The original file is intact: add -i to write the result.
Chain several transformations with -e
Rather than chaining sed | sed | sed, pass several expressions with -e: they apply in order, in a single pass over the file. A semicolon inside one expression ('/^#/d; /^$/d') remains the equivalent for short cases.
sed -E \ -e '/^#/d' \ -e '/^$/d' \ -e 's/[[:space:]]*=[[:space:]]*/=/' \ db.confhost=db.internal.example.comport=5432user=adminpassword=secret123database=productionmax_connections=100connect_timeout=30Three transformations applied in one pass:
- Removal of the comments
- Removal of the empty lines
- Normalisation of the spaces around
=:[[:space:]]*means zero or more spaces, the*quantifier being detailed in the note that follows
Capture groups and backreferences
With -E, parentheses (...) create capture groups, numbered from left to right. The backreferences \1, \2 inject the captured content back into the replacement, which lets you reorder fields without going through awk.
# Swap IP and hostname in hosts.oldsed -E 's/^([0-9.]+)([[:space:]]+)([a-z0-9.]+)/\3\2\1/' hosts.oldweb01.prod.example.com 192.168.1.10 web01web02.prod.example.com 192.168.1.11 web02db01.prod.example.com 192.168.1.20 db01bastion.prod.example.com 10.0.0.5 bastion([0-9.]+)→ group 1: one or more digits or dots, hence the IP([[:space:]]+)→ group 2: one or more spaces or tabs, hence the separator([a-z0-9.]+)→ group 3: one or more lowercase letters, digits or dots, hence the hostname
Without the +, each class takes a single character: ^[0-9.] stops at the 1 of 192.168.1.10, the next character is not a space, the pattern no longer matches and sed leaves the line unchanged, without reporting any error.
In the replacement, \3\2\1 injects back the hostname, the separator and the IP, in the new order. The captured separator keeps its original width, hence the irregular alignment of the output.
Complete workflow: patch a config file for a migration
Here is the complete workflow of a migration from production to staging: a copy of the original, a single sed pass with a backup, then two checks. Each step protects against a different kind of error.
# 1. Make a safety copycp db.conf db.conf.orig
# 2. Apply every transformation in a single passsed -E -i.bak \ -e 's/host[[:space:]]*=[[:space:]]*.*/host=db.staging.example.com/' \ -e 's/database[[:space:]]*=[[:space:]]*.*/database=staging/' \ -e 's/[[:space:]]*=[[:space:]]*/=/' \ -e '/^#/d' \ -e '/^$/d' \ db.conf
# 3. Check the resultcat db.conf
# 4. Check the difference with the originaldiff db.conf.orig db.confResult of cat db.conf:
host=db.staging.example.comport=5432user=adminpassword=secret123database=stagingmax_connections=100connect_timeout=30Result of the diff:
1,3c1,2< # Database configuration< host = db.internal.example.com< port = 5432---> host=db.staging.example.com> port=54325,9c4,7< password= secret123< database=production< max_connections = 100< # timeout in seconds< connect_timeout= 30---> password=secret123> database=staging> max_connections=100> connect_timeout=30The diff after sed -i is your mandatory check before shipping the file: it shows exactly what changed, and immediately reveals a too broad substitution or a pattern that matched nothing.
Extract lines with -n and p
By default, sed prints every line, even unmodified ones. The -n option suppresses this automatic printing, and the p command explicitly prints the selected lines. Combined, they reproduce a grep. Forget -n, and the matching lines come out twice: on this 3-line excerpt, the command prints 4.
# Print only the lines with status 500 in access.logsed -n '/" 500 /p' access.log192.168.1.10 - alice [01/Apr/2026:08:17:30 +0000] "PUT /api/orders/7 HTTP/1.1" 500 0Frequent traps
These traps have one thing in common: sed almost never reports an error. A substitution that finds nothing, a regex written in ERE run in BRE, a dot that matches too broadly: the command succeeds, and only the result gives the problem away. Hence the importance of the final diff.
| Trap | Symptom | Solution |
|---|---|---|
Forgetting /g | Only the first occurrence is replaced | Add the /g flag |
sed -i without a backup | Original file lost in case of mistake | Use -i.bak systematically |
| Mixing up BRE and ERE | +, ? not recognised without a backslash | Add -E for ERE |
Slash / in the pattern | unknown option to `s' | Use another delimiter: s|/old/path|/new/path|g |
Unescaped dot . | Matches too broadly | Escape it: \. for a literal dot |
| Editing the file shipped by the package | Lost at the next package update | Write the change in an override file (/etc/<service>/conf.d/, or systemctl edit for a systemd unit) |
Key points
s/before/after/g: global substitution on each line-i.bak: in-place edit with a backup, always in production- Numeric address
2s/...or regex address/pattern/s/...to target lines /pattern/d: delete the matching lines-e: chain several commands in one pass- Quantifiers:
*zero or more times,+one or more,?optional,{n,m}between n and m times -E: turn on ERE for+,?,{n,m},(...)capture groups\1,\2: inject a captured group back into the replacement-n ... p: print only the selected lines (like grep)- Check with
diffafter everysed -ion a config file
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Process data with awk
A new full quiz with random questions
Work again only on the questions you missed