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 lineP: print the pattern space up to the first newlineD: 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 aN: 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 ofnew.txtafter 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 withd
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)