2026年11月12日

2026年11月12日

Replacing Multiple Lines with sed: N, -z, Ranges and Block Replacement

This article is a localized edition of the Japanese original. Read the Japanese original

sed reads its input one line at a time and processes each line with the trailing newline removed. That is why a pattern containing a newline, such as sed 's/foo\nbar/X/', does not match as written. To replace text across lines, you either append the next line, read the whole file at once, or specify a range of lines.

The commands in this article were tested with GNU sed 4.9 (the version shipped by many Linux distributions as of November 2026). Differences from the macOS sed (BSD sed) are summarized in the last section.

Background: how sed processes text and how to preview changes

sed reads a line into the "pattern space," applies the commands, prints the result and moves to the next line. To replace across lines, you first put several lines into the pattern space and then use the s command.

When you try out a substitution, send the result to standard output without touching the file, and compare it with the original.

sed 's/old_host/new_host/g' app.conf | diff app.conf -

If the diff looks right, overwrite the file with -i (covered below).

Single-line substitution and address ranges

Before getting to multiple lines, here are the basic forms you will often combine with them.

# Replace only the first match on each line
sed 's/foo/bar/' file.txt

# Replace every match on each line
sed 's/foo/bar/g' file.txt

# Only lines 10-20
sed '10,20s/old/new/g' config.conf

# From the line containing BEGIN to the line containing END
sed '/BEGIN/,/END/s/debug/info/g' app.conf

# The line starting with server and the next 5 lines (GNU extension)
sed '/^server/,+5s/timeout 30/timeout 60/' site.conf

# Use a different delimiter such as | when the pattern contains paths
sed 's|/var/www/old|/var/www/new|g' site.conf

An address range only narrows down which lines are processed line by line; it does not match patterns that span a newline. For patterns containing newlines, use the methods in the next sections.

Replacing two consecutive lines (N, P, D)

N appends the next line to the pattern space (with a newline \n in between). However, sed 'N;s/foo\nbar/X/' alone reads the input in fixed pairs (lines 1-2, 3-4, and so on), so it misses a match when foo is on an even-numbered line.

printf 'x\nfoo\nbar\ny\nfoo\nbar\n' > sample.txt

sed 'N;s/foo\nbar/REPLACED/' sample.txt
# x / foo / bar / y / REPLACED  <- foo/bar on lines 2-3 is not replaced

To look at two lines with a window that slides one line at a time, combine $!N, P and D.

sed '$!N;s/foo\nbar/REPLACED/;P;D' sample.txt
# x / REPLACED / y / REPLACED
  • $!N: on every line except the last, append the next line
  • P: print the pattern space up to the first newline
  • D: delete up to the first newline; if anything remains, restart the cycle without reading a new line

To print only the lines that matched, use -n with the p flag.

sed -n '$!N;s/foo\nbar/REPLACED/p' sample.txt

Replacing across the whole file (-z or :a;N;$!ba)

For patterns spanning three or more lines, or an unknown number of lines, it is easier to load the whole file into the pattern space before substituting.

With GNU sed, -z makes the line separator the NUL character instead of a newline. Normal text files contain no NUL, so the entire file is treated as one "line," and \n matches newlines.

sed -z 's/foo\nbar\nbaz/NEW/g' file.txt

Where -z is not available, read all lines with a label and a loop.

sed ':a;N;$!ba;s/foo\nbar\nbaz/NEW/g' file.txt
  • :a: set label a
  • N: append the next line
  • $!ba: if this is not the last line, branch back to label a

Both approaches load the whole file into memory, so they are not suited to very large files such as multi-gigabyte logs. Also, in bash a command containing ! may trigger history expansion inside double quotes, so wrap it in single quotes.

You can also put a newline in the replacement (GNU sed).

echo 'a,b' | sed 's/,/\n/'
# a
# b

Replacing a block between marker lines

Rewriting the part of a config file enclosed by # BEGIN X and # END X can be done with an address range. Take this file as an example.

a
# BEGIN X
old1
old2
# END X
b

Deleting the block

sed '/^# BEGIN X/,/^# END X/d' config.txt
# a / b

Replacing the entire block with other text

Using the c command on a range deletes the lines in the range and prints the given text once. For multi-line text, end each line with \ to continue.

sed '/^# BEGIN X/,/^# END X/c\
# BEGIN X\
new1\
# END X' config.txt

Keeping the markers and replacing only the contents with another file

When the new content is long, write it to a separate file (new.txt here) and read it in with r.

sed -e '/^# BEGIN X/r new.txt' \
    -e '/^# BEGIN X/,/^# END X/{/^# BEGIN X/b' \
    -e '/^# END X/b' \
    -e 'd}' config.txt
  • /^# BEGIN X/r new.txt: print the contents of new.txt after the BEGIN line
  • Inside the range, the BEGIN and END lines are kept with b (branch to the end of the script), and everything else is removed with d

The result is as follows.

a
# BEGIN X
new1
new2
# END X
b

Editing files in place (-i) and applying to multiple files

-i writes the result back to the original file. If you add an extension, as in -i.bak, the original file is kept as config.txt.bak.

sed -i.bak 's/old/new/g' config.txt
diff config.txt.bak config.txt

To apply a change to many files at once, combine sed with find. Check the target files with grep -rl first.

# Check which files will be affected
grep -rl '127.0.0.1' /etc/app --include='*.conf'

# Then rewrite them (a .bak copy is kept for each file)
find /etc/app -name '*.conf' -exec sed -i.bak 's/127\.0\.0\.1/0.0.0.0/g' {} +

In regular expressions, . matches any single character, so escape it as \. when replacing IP addresses or domain names.

When you have many rules, put one per line in a script file and load it with -f.

cat rules.sed
# s/foo/bar/g
# /^#/d

sed -f rules.sed input.txt > output.txt

Differences between GNU sed and macOS (BSD sed)

Item GNU sed (Linux) BSD sed (macOS)
Edit in place without backup sed -i 's/a/b/' f sed -i '' 's/a/b/' f
Edit in place with backup sed -i.bak 's/a/b/' f sed -i .bak 's/a/b/' f or sed -i.bak
-z (NUL-separated) Available A GNU extension; some versions lack it
addr,+N ranges Available A GNU extension; some versions lack it
\n in the replacement Becomes a newline Does not become a newline (you get n or similar)

If you want to use GNU sed syntax on macOS, install the Homebrew gnu-sed package and run it as the gsed command.

Do not use sed on WordPress data

If you replace a domain with sed in a WordPress database dump (.sql), the character counts stored in serialized data (values like s:20:"...") no longer match, and widget or theme settings can fail to load. To replace URLs in the database, use wp search-replace, which handles serialized data.

Related articles

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲