Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

sed: replace and delete text

By updated

Improve this page

When you need to quickly modify the content of several files, perform mass replacements or delete some lines based on a pattern, sed is the essential tool. Designed for stream editing, sed searches, replaces, deletes or inserts text straight from the command line, without opening the files by hand.

What you will learn

  • Replace text with the s/old/new/ command
  • Delete, insert and modify targeted lines
  • Use addresses (line numbers, patterns) to target precisely
  • Edit files in place with -i (safely)

The sed command in the Linux ecosystem

sed belongs to the text-processing commands of Linux. Each tool has its speciality, and sed is the one that edits a stream:

CommandSpecialityWhen to use it
sedEdit text as a streamReplace, delete, insert text
grepSearch for patternsFilter the lines matching a pattern
awkProcess tabular dataExtract/transform columns
trTransliterate charactersReplace character by character
cutExtract columnsSplit by delimiter or position
findSearch for filesFind files by attributes

Frequent combinations:

Terminal window
# grep + sed: filter then transform
grep "ERROR" log.txt | sed 's/ERROR/ALERTE/g'
# sed + awk: modify then extract
sed 's/;/,/g' data.csv | awk -F',' '{print $2}'
# find + sed: modify several files
find . -name "*.conf" -exec sed -i 's/old/new/g' {} +

When sed is the right tool (and when to avoid it)

sed excels on unstructured text, line by line. Its limit is also its definition: it ignores any structure. As soon as the data is hierarchical or nested, sed works blind and produces fragile results. The right reflex is to pick the tool that understands the format.

Data to processAvoid sed, useWhy
JSONjqjq knows the structure; sed breaks on nesting, spaces and key order
YAMLyq (mikefarah)indentation and anchors are meaningful, invisible to a string substitution
HTML / XMLxmllint, xmlstarlet, pupHTML is not a regular language: a regex cannot follow nesting
Multi-line / logicawk or perl -0777fields, variables and state memory that sed lacks

The rule fits in one sentence: sed for plain text, a dedicated tool for structured data. On a minimal server without jq, a one-off sed on JSON can help out, but never make it a production building block.

Understand the sed command in 2 minutes

The mental model below sums up how sed works: an input, a command, an output.

🧠 Mental model: How sed works

sed = Read a line → Apply the commands → Print the result

sed reads its input line by line, applies the command (s/old/new/g, d) and writes the result to standard output, or into the file itself with -i; -n makes sed quiet and -E enables extended regex.sedINPUTfile · stdin, line by lineCOMMANDs/old/new/g · dOUTPUTstdout by defaultfile modifiedwith -i-i in-place editing · -n quiet-E extended regex
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Key points

  • sed reads the file line by line (a stream, not all in memory)
  • Each line goes through the commands in order
  • By default, sed prints the result on stdout (the file is not modified)
  • The s/old/new/ command replaces text
  • Without the g flag, only the first occurrence per line is replaced
  • With -i, sed modifies the file directly (danger!)

Golden rules

1
Test without -i before editing in place Preview the changes with sed '...' file before sed -i '...' file
2
Use a delimiter suited to the content If the text contains /, use # or @ as the delimiter: s#/path#/other#

Essential vocabulary

s/old/new/
Substitution: replaces old with new (1st occurrence)
s/old/new/g
Global substitution (every occurrence)
d
Delete: deletes the line
p
Print: prints the line
-i
In-place: modifies the file directly
-n
No-print: prints nothing except with p
📚 Going further: 12 advanced options
a\text
Append: adds after the line
i\text
Insert: inserts before the line
c\text
Change: replaces the whole line
/regex/
Address: targets the lines matching the regex
1,10
Range: lines 1 to 10
/start/,/end/
Range between two patterns
-E
Extended regex (ERE): +, ?, |, () without escaping
&
In s///, stands for the matched text
\1, \2
Backreferences: captured groups
y/abc/xyz/
Transliteration (like tr)
q
Quit: stops processing
N
Next: appends the next line to the buffer

sed reads the file line by line (a stream), applies the requested transformations, then prints the result. By default, the original file is not modified.

Minimal syntax

Every sed invocation fits in this form. Remember above all the single quotes around the command: they stop the shell from interpreting $, * and \ before sed sees them. With double quotes, sed "$d" becomes an empty command, which copies the file without deleting anything, because bash will first have replaced $d with a non-existent variable. The file is optional: without it, sed reads standard input, which makes it a natural pipeline link.

Terminal window
sed [OPTIONS] 'COMMANDE' [FICHIER]

The 3 parts of a sed command

This breakdown serves for diagnosis: when a sed command does nothing, the error almost always sits in a single one of these three parts. The Required column flags the point most often forgotten: the default output is stdout, so a sed that "does not modify the file" behaves normally, it is simply missing the -i option.

StepQuestionExamplesRequired?
InputWhere does the text come from?fichier.txt, stdin (pipe)One of the two
CommandWhich transformation?s/old/new/g, d, p✓ Yes
OutputWhere does the result go?stdout (default) or file (-i)✗ No (default: stdout)

The s/// substitution command

The most used command is s (substitution), which looks for a pattern and replaces it with a text:

Terminal window
sed 's/ancien/nouveau/' fichier.txt
ElementMeaning
sSubstitution command
/Delimiter (can be #, @, |)
ancienText or regex to search for
nouveauReplacement text

By default, only the first occurrence per line is replaced. To replace every occurrence, add the g flag:

Terminal window
sed 's/foo/bar/g' fichier.txt

The other main commands

Beyond substitution, sed has a handful of one-letter commands. Two of them behave differently from the others: p duplicates the line instead of printing it alone, because sed already prints each line by default; it is therefore always paired with the -n option. And q stops reading the file, which makes it the most efficient command to extract the first lines of a multi-gigabyte file.

CommandEffectExample
dDeletes the line/debug/d
pPrints the line-n '/error/p'
i\texteInserts before the line1i\# En-tête
a\texteAppends after the line$a\# Fin
c\texteReplaces the whole line3c\Nouvelle ligne
qQuits after this line10q

Addresses: target specific lines

By default, sed applies the command to every line. You can target with addresses, placed before the command:

AddressWhat it targetsExample
5Line number 5sed '5d' f.txt
$Last linesed '$d' f.txt
/pattern/Lines containing "pattern"sed '/error/d' f.txt
1,10Lines 1 to 10sed '1,10d' f.txt
/début/,/fin/Range between two patternssed '/BEGIN/,/END/d' f.txt

Substitution flags

Flags go after the last delimiter and stack up (gi replaces everywhere regardless of case). The numeric flag is the least known and the most useful on logs: s/a/X/2 only touches the second occurrence of each line. It also combines with g in the form s/a/X/2g, which replaces from the second occurrence to the end of the line. Watch the i flag: it is a GNU extension, absent from POSIX.

FlagEffectExample
gGlobal (every occurrence)s/a/b/g
iCase-insensitives/error/warning/gi
pPrints if a substitution was made-n 's/a/b/p'
2Replaces the 2nd occurrences/a/b/2

Alternative delimiters

When the text contains /, use another delimiter. The character placed right after the s becomes the delimiter, whatever it is: sed reserves nothing for the slash. Compare the two forms below, they do exactly the same thing; only the second stays readable six months later.

Terminal window
# ✗ Unreadable and error-prone
sed 's/\/var\/log/\/opt\/logs/g' fichier.txt
# ✓ Much clearer with #
sed 's#/var/log#/opt/logs#g' fichier.txt

In-place editing with -i

By default, sed prints the result on stdout without modifying the file. To modify it directly, add -i, ideally with a backup suffix:

Terminal window
# Modifies the file (DANGER on error)
sed -i 's/old/new/g' fichier.txt
# ✓ Creates a backup before modifying
sed -i.bak 's/old/new/g' fichier.txt

Golden rule: always test without -i first!

Terminal window
# 1. Test
sed 's/old/new/g' fichier.txt
# 2. Check the result, then only:
sed -i.bak 's/old/new/g' fichier.txt

Typical errors (and fixes)

These four errors account for most "my sed does not work" tickets. All of them are diagnosed the same way: rerun the command without -i and look at the output. The most misleading case is the last one, "nothing happens": sed never reports the absence of a match and returns a 0 return code, so a script that does not check its result goes on as if the change had been made.

ErrorCauseFix
Only the 1st occurrence is replacedMissing g flagAdd g: s/old/new/g
Syntax error with paths/ in the textUse # as the delimiter
File modified by mistake-i without a prior testAlways test without -i first
Nothing happensNo match (case?)Check with grep or use i (insensitive)

Common substitution patterns

Now that you understand the logic of sed, here are sixteen ready-to-use patterns. Each one gives its general formula and a concrete example.

Simple substitution

Replace the first occurrence of a text on each line. It is the basic form, and also the most common trap: without the g flag, the following occurrences on the same line stay intact.

Terminal window
sed 's/ancien/nouveau/' <fichier> # formula
sed 's/erreur/warning/' log.txt # example
  • ancien: text or regex to search for
  • nouveau: replacement text

Global substitution

Replace every occurrence on each line. The g flag acts inside a line, not on the whole file: sed already processes every line by default.

Terminal window
sed 's/ancien/nouveau/g' <fichier> # formula
sed 's/foo/bar/g' fichier.txt # example
  • g: global flag, every occurrence of the line

Case-insensitive substitution

Replace a text whatever its case. This flag is a GNU extension absent from POSIX, so avoid it in a portable script meant to run on macOS or on an embedded system.

Terminal window
sed 's/ancien/nouveau/gi' <fichier> # formula
sed 's/error/warning/gi' log.txt # example
  • i: case-insensitive flag (GNU sed)

Alternative delimiter

Avoid escapes when the text contains /. Pick a character absent from both the pattern and the replacement, otherwise you move the problem instead of solving it.

Terminal window
sed 's#ancien#nouveau#g' <fichier> # formula
sed 's#/var/log#/opt/logs#g' paths.txt # example
  • #: alternative delimiter (#, @, etc.) instead of /

Delete lines by pattern

Delete the lines containing a pattern. The deletion covers the whole line as soon as a match is found anywhere in it, not only at the start of the line.

Terminal window
sed '/motif/d' <fichier> # formula
sed '/DEBUG/d' log.txt # example
  • motif: regex pattern to match
  • d: delete command

Delete empty lines

Clean up a file by removing the empty lines. Watch out, ^$ only matches strictly empty lines: a line containing a space or a tab survives, you then need /^[[:space:]]*$/d.

Terminal window
sed '/^$/d' <fichier> # formula
sed '/^$/d' texte.txt # example
  • ^$: regex of an empty line (start = end)

Target by line number

Apply a command only to a specific line. The number is counted over all the files passed as arguments, not per file: with several files, line 5 only exists once, except with the -s or -i option, which process each file separately.

Terminal window
sed 'N<commande>' <fichier> # formula
sed '5d' fichier.txt # example: deletes line 5
  • N: targeted line number

Range of lines

Apply a command to a set of lines. Both bounds are included, and $ can replace the upper bound to go to the end of the file (2,$d).

Terminal window
sed 'N,M<commande>' <fichier> # formula
sed '2,5d' fichier.txt # example: deletes lines 2 to 5
  • N,M: from line N to line M inclusive

Range between two patterns

Target the lines located between two patterns. Both delimiting lines are included in the range. If the end pattern never appears, the range runs to the end of the file.

Terminal window
sed '/début/,/fin/<commande>' <fichier> # formula
sed '/BEGIN/,/END/d' fichier.txt # example
  • /début/,/fin/: range delimited by two patterns

In-place editing

Modify the file directly. No confirmation is requested and no going back is possible: always run the command without -i first to check the output.

Terminal window
sed -i '<commande>' <fichier> # formula
sed -i 's/old/new/g' config.conf # example
  • -i: in-place, modifies the file. Always test without -i first.

In-place editing with a backup

Modify a file while keeping a backup copy. The suffix is glued to the option, without a space: -i.bak works, -i .bak fails, because GNU sed then takes .bak for the script and answers unknown command: '.'.

Terminal window
sed -i.bak '<commande>' <fichier> # formula
sed -i.bak 's/old/new/g' config.conf # example
  • -i.bak: creates fichier.bak before modifying

Insert a line

Add text before a line. The condensed form on a single line is a GNU extension; the POSIX version requires a line break after the backslash.

Terminal window
sed '<adresse>i\<texte>' <fichier> # formula
sed '1i\# En-tête ajouté' fichier.txt # example
  • i\: insert command, adds before the targeted line

Append after a line

Add text after a line. Combined with the $ address, it adds a line at the end of the file, a handy move to complete a configuration without opening it in an editor.

Terminal window
sed '<adresse>a\<texte>' <fichier> # formula
sed '/END/a\# Commentaire' fichier.txt # example
  • a\: append command, adds after the targeted line

Extract the lines of a pattern, like grep. The -n option goes hand in hand with p: without it, sed already prints each line and the matched lines appear twice.

Terminal window
sed -n '/motif/p' <fichier> # formula
sed -n '/ERROR/p' log.txt # example
  • -n: disables automatic printing
  • p: print command, prints the line

Backreferences (captured groups)

Reuse a captured part in the replacement. In basic regex, group parentheses are escaped (\( and \)); with the -E option, they are written without a backslash.

Terminal window
sed 's/\(motif\)/\1 ajout/' <fichier> # formula
sed 's/\([0-9]*\)/ID:\1/' fichier.txt # example
  • \(\): capture group
  • \1: reference to the first captured group

Reuse the matched text (&)

Insert the matched text in the replacement with &. To insert a literal &, escape it as \&, otherwise sed copies the matched text in its place.

Terminal window
sed 's/motif/[&]/g' <fichier> # formula
sed 's/[0-9]\+/(&)/g' fichier.txt # example
  • &: stands for the whole matched text

Pitfalls to avoid

Seven errors come back constantly with sed. For each one: the faulty command, the symptom, the cause and the correction.

Forgetting the g flag

Without the g flag, sed stops at the first occurrence of each line.

Terminal window
sed 's/foo/bar/' fichier.txt # ✗ only replaces the 1st occurrence per line

Symptom: some occurrences are not replaced. Cause: without g, sed only touches the first occurrence of each line.

Terminal window
sed 's/foo/bar/g' fichier.txt # ✓ global flag

Slash in the text to replace

A slash in the text collides with the default delimiter of the s command.

Terminal window
sed 's//var/log//opt/logs/' fichier.txt # ✗ syntax error

Symptom: unknown option to 's' error or unexpected result. Cause: the / of the text are taken for delimiters.

Terminal window
sed 's#/var/log#/opt/logs#g' fichier.txt # ✓ alternative delimiter

-i editing without a preview

-i writes straight to the file, without any undo.

Terminal window
sed -i 's/important/supprimé/g' fichier.conf # ✗ run without a test

Symptom: lost data, corrupted file. Cause: -i modifies the file without any possible going back.

Terminal window
sed 's/old/new/g' fichier # ✓ test first, then sed -i.bak

Unescaped regex metacharacter

The dot of a file name is a regex metacharacter in the pattern.

Terminal window
sed 's/fichier.txt/fichier.log/' fichier # ✗ the . matches any character

Symptom: unexpected matches (fichierXtxt is matched too). Cause: the dot . is a regex metacharacter.

Terminal window
sed 's/fichier\.txt/fichier.log/g' fichier # ✓ escaped dot

-i incompatibility between GNU and BSD

The same -i does not take the same argument on GNU and BSD.

Terminal window
sed -i 's/old/new/' fichier # ✗ fails on macOS (BSD sed)

Symptom: invalid command code error on macOS. Cause: BSD sed requires an extension after -i, even an empty one.

Terminal window
sed -i '' 's/old/new/' fichier # ✓ on macOS, or install GNU sed

Line break in the replacement

\n in the replacement is not defined by POSIX.

Terminal window
sed 's/$/\n/' fichier # ✗ works with GNU sed, not guaranteed elsewhere

Symptom: the command does produce an empty line after each line on a Linux server, then a literal n appears on a colleague's macOS workstation. Cause: POSIX does not define the meaning of \n in the replacement part. GNU sed reads it as a real line break, BSD implementations do not guarantee it. For a script that must run everywhere, inject the line break from the shell.

Terminal window
sed 's/$/\'$'\n''/' fichier # ✓ portable: the line break comes from bash ($'\n')

Greedy regex (.* too broad)

.* is greedy and swallows as much text as it can.

Terminal window
sed 's/<.*>//g' fichier # ✗ deletes from the first < to the last >

Symptom: <a>texte<b> becomes empty instead of texte. Cause: .* is greedy, it matches as much as possible.

Terminal window
sed 's/<[^>]*>//g' fichier # ✓ negated class: stops at the first >

-i replaces the path it receives, even when it is a symbolic link.

Terminal window
sed -i 's/old/new/' /etc/service/config # ✗ if config is a symlink, the link is lost

Symptom: the symbolic link disappears, replaced by a regular file, and the original target stays unchanged. Cause: -i writes a temporary file then renames it onto the path, which destroys the link.

Terminal window
sed -i --follow-symlinks 's/old/new/' /etc/service/config # ✓ GNU: follows the link

The \d false friend

\d does not mean digit in sed.

Terminal window
sed 's/\d/X/g' fichier # ✗ \d does NOT mean "digit" in sed

Symptom: letters are replaced instead of digits (\d matches the letter d). Cause: unlike PCRE regex, sed does not know \d. Fix: the portable POSIX class.

Terminal window
sed 's/[[:digit:]]/X/g' fichier # ✓ or [0-9]

Windows line endings (CRLF)

A Windows file hides a \r at the end of each line.

Terminal window
sed 's/local$/remote/' config.txt # ✗ fails if the file is in CRLF

Symptom: no replacement although the pattern looks right. Cause: an invisible \r stays at the end of the line, between local and the real end, so the local$ pattern never matches. Fix: remove the \r first.

Terminal window
sed 's/\r$//' config.txt # ✓ normalises to LF (GNU)

Hands-on lab

Nothing replaces practice. Create the test environment below, then go through the eight steps. At the end, clean up with rm -rf ~/sed-lab.

Prepare the ground

This script creates five test files in ~/sed-lab: a configuration, a log, a CSV, a list of paths and a "draft" file to clean up.

Terminal window
# Create the lab
mkdir -p ~/sed-lab
cd ~/sed-lab
# Typical configuration file
cat > config.conf << 'EOF'
# Configuration serveur
server_host=localhost
server_port=8080
debug_mode=true
log_level=INFO
database_url=postgres://localhost:5432/mydb
max_connections=100
EOF
# Log file
cat > access.log << 'EOF'
192.168.1.10 - - [15/Jan/2025:10:30:00] "GET /index.html HTTP/1.1" 200 1234
192.168.1.20 - - [15/Jan/2025:10:30:05] "POST /api/users HTTP/1.1" 201 567
192.168.1.10 - - [15/Jan/2025:10:30:10] "GET /style.css HTTP/1.1" 200 890
192.168.1.30 - - [15/Jan/2025:10:30:15] "GET /error HTTP/1.1" 404 123
192.168.1.10 - - [15/Jan/2025:10:30:20] "DELETE /api/users/5 HTTP/1.1" 403 45
EOF
# CSV file
cat > users.csv << 'EOF'
id,nom,email,role
1,Alice,alice@example.com,admin
2,Bob,bob@example.com,user
3,Charlie,charlie@example.com,user
4,Diana,diana@example.com,moderator
EOF
# File with paths
cat > paths.txt << 'EOF'
/var/log/syslog
/home/user/documents/report.pdf
/etc/nginx/nginx.conf
/usr/local/bin/script.sh
EOF
# File to clean up
cat > messy.txt << 'EOF'
Ligne avec espaces au début
Ligne normale
Autre ligne avec espaces
Ligne vide après
Encore du texte
Espaces partout
EOF
echo "✓ Lab créé dans ~/sed-lab"
ls -la ~/sed-lab

The eight steps

These eight commands run without -i except the seventh: they print the result on screen and leave the lab files intact. Get into the habit of comparing the output with the original file with diff <(sed ...) fichier before moving to the next step. The progression goes from simple replacement to targeting by address, which is the real skill to acquire: knowing how to restrict a transformation to a few lines avoids damage to configuration files.

  1. Simple substitution. Replace localhost with 127.0.0.1 in config.conf.

    Terminal window
    sed 's/localhost/127.0.0.1/' ~/sed-lab/config.conf

    s/ancien/nouveau/ replaces the first occurrence on each line. For every occurrence: add the g flag.

  2. Alternative delimiter. Replace /var/log with /opt/logs in paths.txt.

    Terminal window
    sed 's#/var/log#/opt/logs#' ~/sed-lab/paths.txt

    When the text contains /, use another delimiter (# here).

  3. Delete lines. Delete the lines containing 404 in access.log.

    Terminal window
    sed '/404/d' ~/sed-lab/access.log

    /motif/d deletes the lines that contain the pattern.

  4. Delete empty lines. Remove the empty lines of messy.txt.

    Terminal window
    sed '/^$/d' ~/sed-lab/messy.txt

    ^$ matches an empty line (start immediately followed by end).

  5. Target a line by number. Replace user with admin only on line 3 of users.csv.

    Terminal window
    sed '3s/user/admin/' ~/sed-lab/users.csv

    The number placed before the command restricts it to that line.

  6. Range of lines. Replace @example.com with @company.org on lines 2 to 4 of users.csv.

    Terminal window
    sed '2,4s/@example.com/@company.org/' ~/sed-lab/users.csv

    N,M targets the lines from N to M inclusive.

  7. In-place editing with a backup. Modify config.conf by replacing true with false, keeping a backup.

    Terminal window
    sed -i.bak 's/true/false/' ~/sed-lab/config.conf

    -i.bak modifies the file and creates config.conf.bak beforehand.

  8. Remove leading spaces. Remove the spaces at the start of each line of messy.txt.

    Terminal window
    sed 's/^[[:space:]]*//' ~/sed-lab/messy.txt

    ^[[:space:]]* matches zero or more spaces at the start of the line.

Progressive exercises

Eight exercises to validate your mastery. All of them work on the files of the ~/sed-lab lab. Search on your own first, then unfold the solution to check yourself.

Foundation level

These first three exercises only use one command at a time: substitute, change the delimiter, delete lines. Write your answer before opening the solution, then run it on the lab: a command that looks right on paper often fails on a delimiter or escaping detail.

Exercise 1, basic substitution. Replace every error with warning in access.log. Hint: do not forget the g flag.

Show the solution
Terminal window
sed 's/error/warning/g' ~/sed-lab/access.log

The g flag replaces every occurrence, not only the first one of each line.

Exercise 2, replace paths. Replace /usr/local with /opt in paths.txt. Hint: the / of the text get in the way of the default delimiter.

Show the solution
Terminal window
sed 's#/usr/local#/opt#g' ~/sed-lab/paths.txt

# is used as the delimiter to avoid escaping the /.

Exercise 3, delete comments. Delete every line starting with # in config.conf. Hint: ^ stands for the start of line, d deletes.

Show the solution
Terminal window
sed '/^#/d' ~/sed-lab/config.conf

/^#/ matches the lines starting with #; d deletes them.

Composition level

The next three exercises combine a regex and an address. It is the hard part: you have to describe a position (end of line, line number) and no longer just a word. If your command returns nothing, first check the pattern alone with grep before blaming sed.

Exercise 4, clean up spaces. Remove the trailing spaces of messy.txt. Hint: [[:space:]] matches spaces, $ the end of line.

Show the solution
Terminal window
sed 's/[[:space:]]*$//' ~/sed-lab/messy.txt

[[:space:]]*$ matches zero or more spaces right before the end of the line.

Exercise 5, modify a specific line. Change the whole content of line 3 of users.csv to MODIFIÉ. Hint: c\ replaces a whole line.

Show the solution
Terminal window
sed '3c\MODIFIÉ' ~/sed-lab/users.csv

3c\ targets line 3; c\ replaces its whole content.

Exercise 6, extract lines (like grep). Print only the lines of access.log containing 404. Hint: -n disables automatic printing, p prints the matched lines.

Show the solution
Terminal window
sed -n '/404/p' ~/sed-lab/access.log

-n cuts the default printing; /404/p only prints the matching lines.

Advanced level

The last two exercises touch on the moves found in production scripts: prefixing a stream and modifying a file in place. The second is the only one of the series that writes to disk; check that the .bak file is there after running it, it is the proof that the safety net worked.

Exercise 7, add a prefix to each line. Add LOG: at the start of each line of access.log. Hint: ^ matches the start of line.

Show the solution
Terminal window
sed 's/^/LOG: /' ~/sed-lab/access.log

s/^/LOG: / replaces the "start of line" with LOG: , so it prefixes each line.

Exercise 8, safe in-place editing. Replace localhost with 127.0.0.1 in config.conf while keeping a backup. Hint: -i.bak creates the backup automatically.

Show the solution
Terminal window
sed -i.bak 's/localhost/127.0.0.1/g' ~/sed-lab/config.conf

-i.bak modifies the file in place and creates config.conf.bak as a safety net.

Troubleshooting

When a sed substitution does not work, the problem most often comes from the pattern or the delimiters. Here is how to diagnose it, then how to read the error messages.

Diagnostic methods

These four commands run in order: they progressively isolate the origin of the problem. The third one, sed -n '/pattern/=', prints the matched line numbers and answers the only question that matters at first: does the pattern match anything? If it returns nothing, the problem is in the regex, not in the substitution. If it returns numbers but the replacement does not show up, the error is in the right-hand part of the s///.

Terminal window
# Always preview before using -i
sed 's/old/new/g' fichier
# See which lines a pattern matches
sed -n '/pattern/p' fichier
# Print the numbers of the matched lines
sed -n '/pattern/=' fichier
# Validate the pattern on its own with grep
grep 'pattern' fichier

Frequent errors

Unlike the absence of a match, these situations produce an explicit error message and a non-zero return code, so a script with set -e stops on them. Read the message in full: sed gives the faulty character number in the expression, which immediately locates the missing delimiter.

Error messageProbable causeFix
unterminated 's' commandMissing closing delimiterMake sure you have exactly 3 delimiters: s/ancien/nouveau/
unknown option to 's'Unescaped / in the text, read as a delimiterChange the delimiter: s#ancien#nouveau#
unknown command: 'X'Unknown command letter, or misplaced script (-i .bak)Check the command letter and glue the suffix: -i.bak
invalid option -- 'Z'Option that does not exist in this sedCheck sed --version; on macOS, install GNU sed
no input files-i without a file: in-place editing does not apply to a pipePass the file as an argument
invalid command codeMessage of macOS's BSD sed, often -i without a suffixWrite sed -i '' on macOS, or install GNU sed

sed in DevOps workflows

sed is everywhere in automation, but a few reflexes avoid incidents in CI and in configuration management, starting with portability.

The GNU versus BSD trap in CI

sed -i 's/a/b/' fichier works on Linux (GNU sed) but fails on macOS (BSD sed), where -i expects a backup suffix right after it. A pipeline that runs on both Linux and macOS runners must use a portable form:

Terminal window
sed -i.bak 's/a/b/' fichier && rm fichier.bak # portable GNU + BSD
# or, even more robust:
perl -pi -e 's/a/b/' fichier

sed -i is not idempotent: prefer Ansible

In configuration management, a sed -i run through the shell module is replayed on every run and always reports changed: Ansible cannot know whether the change is already applied, and a line addition ends up duplicated. To edit a configuration file durably, the lineinfile and replace modules are declarative and idempotent, handle --check/--diff and create a backup. Keep sed for one-off transformations, not for the state of a server.

Containers and templating

In a Dockerfile, a RUN sed -i freezes the change in an image layer. Prefer configuring at runtime (ENV, variables, entrypoint script) so that a single image serves every environment. To inject variables into a file (${VAR}), envsubst (in French) is safer than sed (no delimiter problem, no injection risk); as soon as you need conditions or loops, move to a real template engine such as Jinja2.

sed cheat sheet

The five tables that follow gather what is used daily, sorted by nature: options, addresses, commands, regex, delimiters. The entries marked GNU do not exist on macOS nor on embedded BusyBox systems, to check before slipping them into a shared script.

Syntax and options

Options go before the command. Remember the -n and p pair: it is the combination that turns sed into an extraction tool. The -E option also deserves your attention, because it switches to extended regex and removes tedious escapes: \(...\) becomes (...), and \+ becomes +.

ElementRoleExample
sed 'cmd' fichierApplies a command to the filesed 's/a/b/' f.txt
sed -e 'cmd1' -e 'cmd2'Chains several commandssed -e 's/a/b/' -e 's/c/d/'
cmd | sed 'cmd'Processes the input of a pipecat f.txt | sed 's/a/b/'
-iIn-place editing (modifies the file)sed -i 's/a/b/' f.txt
-i.bakIn-place editing with a backupsed -i.bak 's/a/b/' f.txt
-nQuiet mode (nothing by default)sed -n '/err/p' f.txt
-E (or -r)Enables extended regex (ERE)sed -E 's/[0-9]+/X/g'
-f script.sedReads the commands from a filesed -f cmds.sed fichier

Addresses (targeting)

The address goes in front of the command and restricts its scope. Only one entry of the table is a GNU extension absent from POSIX: 1~2, which selects every other line. Negation ! on the other hand is standard, and it is the most useful in practice: /DEBUG/!d keeps only the debug lines, which avoids writing the inverse regex, often impossible to phrase cleanly.

AddressRoleExample
5Line number 55s/a/b/
$Last line$d
/regex/Lines matched by the regex/error/d
1,10Lines 1 to 101,10s/a/b/
/début/,/fin/Range between two patterns/BEGIN/,/END/d
1~2Line 1 then every other line (GNU)1~2d
!Inverts the address (NOT)/DEBUG/!d

Main commands

The three insertion commands i\, a\ and c\ are the ones that cause the most portability problems: GNU accepts the text on the same line (1i\# Header), whereas the strict POSIX form requires a line break after the backslash. If an insertion script fails on another system, that is almost always where the problem comes from.

CommandRoleExample
s/old/new/Substitutes (1st occurrence)s/error/warning/
s/old/new/gSubstitutes (every occurrence)s/foo/bar/g
s/old/new/giSubstitutes regardless of cases/error/warning/gi
dDeletes the line/^#/d
pPrints the line-n '/error/p'
qQuits after this line10q
=Prints the line number/error/=
i\texteInserts a line before1i\# Header
a\texteAppends a line after$a\# Footer
c\texteReplaces the whole line3c\Nouvelle ligne

Regex and references

sed uses basic regex (BRE) by default, not the PCRE syntax of programming languages. Concretely, group parentheses are written \(...\) and not (...). GNU does accept \w and \s, but they are non-portable extensions, and \d does not mean "digit" there: prefer the POSIX classes [[:digit:]], [[:alnum:]] and [[:space:]], which work on every implementation. The -E option switches to extended regex and brings back parentheses without backslashes.

ElementRoleExample
&The matched text, in the replacements/[0-9]*/(&)/
\1, \2…Captured groupss/\(.*\)/[\1]/
\(\)Capture group (basic regex)s/\(mot\)/[\1]/
^ / $Start / end of lines/^/PREFIX: /
.Any characters/a.c/X/
[abc] / [^abc]Class / negated classs/[^0-9]//g

Alternative delimiters

The character that immediately follows the s becomes the delimiter: sed does not impose the /, it is a mere convention. Pick a character absent from the pattern and the replacement, otherwise you are back to the original problem. # suits file paths, @ or , suit URLs containing //. A well-chosen delimiter removes every backslash and keeps the command readable six months later.

FormRoleExample
s#old#new## delimiter (ideal for paths)s#/var#/opt#g
s@old@new@@ delimiters@http://@https://@g
s|old|new|| delimiters|foo|bar|g

Mastery checklist

You master sed when you can tick each of these points, from substitution to in-place editing and diagnosis:

  • I can use s/old/new/ to substitute
  • I can use the g flag to replace globally
  • I can use an alternative delimiter (#, @)
  • I can delete lines with d
  • I can target lines by number or by pattern
  • I can use -n and p to extract lines
  • I can edit in place with -i and -i.bak
  • I can insert or append a line with i\ and a\
  • I can use backreferences (\1, &)
  • I can diagnose a substitution that does not work

Key points

  • sed edits plain text line by line: s/// substitution, d deletion, addresses to target.
  • The g flag replaces every occurrence of a line; without it, only the first one.
  • Always preview without -i, then -i.bak to keep a safety net.
  • For structured data, change tools: jq (JSON), yq (YAML), awk or perl (multi-line).
  • In CI, beware of GNU versus BSD (-i); in configuration management, prefer Ansible lineinfile (idempotent).
  • Classic traps: symlink broken by -i, the \d false friend, CRLF line endings.

Knowledge check

Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

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

FAQ: frequently asked questions

The answers below cover the questions that come up most often about sed, from substitution to in-place editing and regular expressions.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn