sed Command: String Substitution, Deleting Lines and Using the -i Option

Test environmentRHEL familyAlmaLinux 9.8root accountChecked 2026-10-28

The sed command is a Linux tool that reads a file line by line and runs substitutions, deletions and insertions, so you can change values in a configuration file without opening an editor. When we ran sed on a copy of a WordPress wp-config.php on an AlmaLinux 9 server, we could change the database name while keeping the original with -i.bak.

The error messages in this article are shown as printed under an English locale.

1️⃣ Basic sed command usage and s substitution

The most commonly used s command substitutes strings in the form [s/search string/replacement string/]. It replaces only the first match on a line.

echo 'http://example.com http://example.com/blog' | sed 's/http:/https:/'
https://example.com http://example.com/blog

To replace every match on a line, add the g flag at the end.

echo 'http://example.com http://example.com/blog' | sed 's/http:/https:/g'
https://example.com https://example.com/blog

If you add a file name at the end, sed reads that file and prints the changed result. On a copy of WordPress's wp-config-sample.php made as wp-config.php, we changed the DB_USER value and checked just that line with grep.

sed 's/username_here/wp_user/' wp-config.php | grep DB_USER
define( 'DB_USER', 'wp_user' );

Since the value changed in the output, it is easy to assume the file changed too, but without options sed only prints the result to the screen and leaves the original file as it is. When we looked up the same line in the file again, the original value was still there.

grep DB_USER wp-config.php
define( 'DB_USER', 'username_here' );

To change the original file as well, use the -i option from 4️⃣.

2️⃣ Selecting lines with sed by line number and pattern (-n, p)

Without an address, the s command applies to every line, so to work on specific lines, write a line number or pattern as an address in front of the command. -n turns off automatic printing and p prints the selected lines. Used together, they show line 23 alone.

sed -n '23p' wp-config.php
define( 'DB_NAME', 'database_name_here' );

Joining two line numbers with a comma prints that range. Lines 23 to 32 are the section with the database name, user, password and host settings.

sed -n '23,32p' wp-config.php
define( 'DB_NAME', 'database_name_here' );

/** Database username */
define( 'DB_USER', 'username_here' );

/** Database password */
define( 'DB_PASSWORD', 'password_here' );

/** Database hostname */
define( 'DB_HOST', 'localhost' );

If you write a pattern between slashes instead of a line number, every line containing that pattern is selected.

sed -n '/DB_/p' wp-config.php
define( 'DB_NAME', 'database_name_here' );
define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

The [=] command prints the number of the selected line. The address $ means the last line, so [$=] shows the total number of lines in the file.

sed -n '/DB_HOST/=' wp-config.php
32
sed -n '$=' wp-config.php
102

An address can also go in front of the s command. Below is the result of changing localhost only on line 32 and printing only the changed line with the p flag.

sed -n '32s/localhost/127.0.0.1/p' wp-config.php
define( 'DB_HOST', '127.0.0.1' );

Running p without -n printed the selected line one more time. sed reads lines one at a time into the pattern space, runs the commands, then prints the pattern space automatically, so line 2 printed by p appears again through automatic printing.

sed '2p' wp-config.php | head -n 4
<?php
/**
/**
 * The base configuration for WordPress

This line-at-a-time method is like a factory conveyor belt that places one part at a time on a workbench, processes it, and moves on to the next part.

You can compare what each command prints for each line, step by step, in the diagram below.

🧭 sed pattern space and automatic printing: p and d on line 2
Explainer diagram
⚙️ Command
$ sed '2p'-n '2p''2d' wp-config.php
Line readRunning the command and output
Line 1 → pattern space<?php
➜
Differs from address 2: no commandAutomatic print onceNo output because of -n
Line 2 → pattern space/**
➜
Matches address 2: run pMatches address 2: run dOnce by p + once by automatic printPrinted once by p onlyClear the pattern space, go to the next line
Line 3 → pattern space * The base configuration for WordPress
➜
Differs from address 2: no commandAutomatic print onceNo output because of -n
🖥️ What appears on screen (lines 1~3)
<?php/**/** * The base configuration for WordPress
p prints the pattern space one more time, so without -n, line 2 is printed twice.
-n turns off automatic printing, so only line 2 selected by p is printed once.
d clears the pattern space and moves to the next line, so line 2 is not printed automatically.

3️⃣ Deleting and inserting lines with sed (d, i, a)

d deletes the selected lines. To view the nginx sample configuration below without comment lines, select lines that start with # or have only spaces before # using the pattern [^ *#].

cat example.conf
# example.com
server {
    listen 80;
    server_name example.com www.example.com;

    # document root
    root /var/www/example.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}
sed '/^ *#/d' example.conf
server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

Joining commands with a semicolon also deletes [^$], which means an empty line. The two empty lines left after deleting the comment lines disappeared, and only the configuration lines remained.

sed '/^ *#/d; /^$/d' example.conf
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html;
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

i inserts text before the selected line and a inserts it after the selected line. The result of inserting one comment line before line 1 is shown below.

sed '1i # managed by sed' example.conf | head -n 3
# managed by sed
# example.com
server {

It is easy to assume that indented text after a keeps its indentation, but GNU sed skips the spaces right after a and inserts only the text.

sed '/index index.php/a    client_max_body_size 64M;' example.conf
# example.com
server {
    listen 80;
    server_name example.com www.example.com;

    # document root
    root /var/www/example.com;
    index index.php index.html;
client_max_body_size 64M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

To keep the indentation, add a backslash after a. Everything from the space after the backslash goes in as text.

sed '/index index.php/a\    client_max_body_size 64M;' example.conf
# example.com
server {
    listen 80;
    server_name example.com www.example.com;

    # document root
    root /var/www/example.com;
    index index.php index.html;
    client_max_body_size 64M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

c replaces the selected line entirely. Below are the first four lines of the result of changing the listen 80 line to 8080.

sed '/listen 80;/c\    listen 8080;' example.conf | head -n 4
# example.com
server {
    listen 8080;
    server_name example.com www.example.com;

4️⃣ Editing files in place with sed -i and backup file names

-i saves the result to the original file instead of the screen. Text attached right after -i is added to the end of the backup file name. wp-config.php and other files the site reads are best edited with -i.bak, keeping the original.

cp wp-config-sample.php wp-config.php
sed -i.bak 's/database_name_here/wordpress_db/' wp-config.php

Running with -i prints nothing to the screen, so check the files with ls -l.

ls -l wp-config.php*
-rw-r--r-- 1 root root 3333 Oct 28 15:13 wp-config.php
-rw-r--r-- 1 root root 3339 Oct 28 15:13 wp-config.php.bak

The edited file and a .bak file now exist side by side. The size is 6 bytes smaller because database_name_here became the shorter wordpress_db. Comparing the two files with diff shows only the changed line.

diff wp-config.php.bak wp-config.php
23c23
< define( 'DB_NAME', 'database_name_here' );
---
> define( 'DB_NAME', 'wordpress_db' );

The changed line is the single DB_NAME line on line 23.

If you have several values to change, chain scripts with -e and run them at once.

sed -i -e 's/username_here/wp_user/' -e 's/password_here/wp_pass/' wp-config.php
grep -n -e DB_USER -e DB_PASSWORD wp-config.php
26:define( 'DB_USER', 'wp_user' );
29:define( 'DB_PASSWORD', 'wp_pass' );

If you join -i and -e as [-ie], the e is read as part of the backup name, and a wp-config.phpe file appeared as shown below.

sed -ie 's/127.0.0.1/localhost/' wp-config.php
ls wp-config.php*
wp-config.php
wp-config.php.bak
wp-config.phpe

-i writes the edited content to a new file and then renames it over the original name, so the file's inode number differs before and after the run.

ls -i wp-config.php
207618887 wp-config.php
sed -i 's/localhost/127.0.0.1/' wp-config.php
ls -i wp-config.php
207618883 wp-config.php

The inode number changed from 207618887 before the run to 207618883.

If the file does not exist or you leave out the file name, sed ends with the error below and no file changes.

sed -i 's/x/y/' no-such.php
sed: can't read no-such.php: No such file or directory
sed -i 's/x/y/'
sed: no input files

5️⃣ sed regular expressions, changing the delimiter and options

🔲 Regular expressions and changing the delimiter

When you change a value containing slashes, such as a path, the s command's delimiter gets mixed up with the slashes in the value, so each slash in the value needs a backslash.

sed -n 's/\/var\/www\/example.com/\/srv\/www\/example.com/p' example.conf
    root /srv/www/example.com;

The character right after s becomes the delimiter, so changing to |, which is not in the value, gives the same result without backslashes.

sed -n 's|/var/www/example.com|/srv/www/example.com|p' example.conf
    root /srv/www/example.com;

A dot (.) in a pattern matches any single character. Below is the result of a pattern looking for example.com that also changed exampleXcom.

echo 'exampleXcom example.com' | sed 's/example.com/test.org/g'
test.org test.org

To find a literal dot, put a backslash in front of it.

echo 'exampleXcom example.com' | sed 's/example\.com/test.org/g'
exampleXcom test.org

A part wrapped in parentheses is recalled in the replacement string with [\1]. With -E, you can use parentheses and + without backslashes. Below is the result of keeping the number after listen and appending default_server.

sed -nE 's/listen ([0-9]+);/listen \1 default_server;/p' example.conf
    listen 80 default_server;

Running the same script without -E reads the parentheses as literal text, giving an error that there is no group to recall. To work without -E, put backslashes on the parentheses and +.

sed -n 's/listen ([0-9]+);/listen \1 default_server;/p' example.conf
sed: -e expression #1, char 46: invalid reference \1 on `s' command's RHS
sed -n 's/listen \([0-9]\+\);/listen \1 default_server;/p' example.conf
    listen 80 default_server;

[&] in the replacement string means all the text the pattern matched, so you can add # in front of the matched part to turn that line into a comment.

sed -n 's/server_name .*;/# &/p' example.conf
    # server_name example.com www.example.com;

🔲 sed command options

📌 Main options: results from this article

OptionTaskResult in this article
-nTurns off automatic printing and prints only lines selected with psed -n '23p' shows line 23 alone; without -n, 2p prints line 2 twice
-iSaves the result to the original file instead of the screen (text right after it is added to the end of the backup name)-i.bak creates wp-config.php.bak; the inode number changes after the run
-eChains several scripts and runs them at onceDB_USER and DB_PASSWORD change at once; joined as -ie, a wp-config.phpe backup appears
-EUses extended regular expressions (no backslashes on parentheses and +)Prints listen 80 default_server;, and the same script without -E gives an [invalid reference] error
--follow-symlinksWith -i, edits the original file the link points toWithout it, a regular file replaces the link and the original stays the same; with it, the original value changes

📎 Other options: per the official manual

OptionTask
-fReads the script from a file and runs it
-rExtended regular expressions, the same as -E (-E is more portable)
-sTreats each file separately instead of as one continuous stream
-zSplits lines on the null character instead of a newline
-uReads input in smaller amounts and flushes output more often
--posixTurns off all GNU extensions
--debugPrints the execution in expanded form

🔲 sed command simulator

The working folder of the simulator below holds a copy of wp-config.php and example.conf. You can type the commands so far yourself, and check files changed with -i using ls -l and diff.

sed command simulatorGNU sed
[root@server work]#
Use the ↑↓ keys to recall commands you typed earlier.

🔢 FAQ & Recommended Content

WordPress's wp-config-sample.php has CRLF line endings, so an invisible [\r] sits right before the end of each line. [/false );$/] printed no lines, while on a copy with [\r] removed by [s/\r$//], the same pattern found the WP_DEBUG line.

-i creates a new regular file in place of the link and saves there, so the original the link pointed to did not change. To edit the original file, also specify [--follow-symlinks]. In that case the link stayed and the original value changed.

Even a variable set with NEW=wp_user is not expanded by the shell inside single quotes, so [$NEW] goes in as literal text. When the script was wrapped in double quotes, the shell expanded the variable first, and wp_user went in where [$NEW] was.

🧭 Text commands: recommended reading 9 posts · 2 references
🗂️ Text viewing · editing commands
📄 grep command 📄 sort command 📄 uniq command 📄 tee command 📄 sed command
📄 cat command 📄 less command 📄 head · tail commands 📄 vi · vim commands

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 6   +   9   =