2026年5月17日
2026年5月17日
sedで複数行を置換する方法|N・-z・範囲指定・ブロックの差し替え
sed は入力を1行ずつ読み、行末の改行を外した状態で処理します。そのため sed 's/foo\nbar/X/' のように改行を含むパターンを書いても、そのままでは一致しません。複数行を置換するには、次の行を読み足す、ファイル全体を一度に読む、行の範囲を指定する、のどれかを使います。
この記事のコマンドは GNU sed 4.9(2026年11月時点で多くの Linux ディストリビューションが採用している版)で動作を確かめています。macOS の sed(BSD sed)との違いは最後の節にまとめます。
前提: sed の処理単位と確認の仕方
sed は1行を「パターンスペース」に読み込み、コマンドを当てて出力し、次の行へ進みます。複数行を置換するときは、パターンスペースに複数の行を入れてから s コマンドを使います。
置換を試すときは、ファイルを書き換えずに標準出力へ出し、元のファイルとの差分を見ます。
sed 's/old_host/new_host/g' app.conf | diff app.conf -
差分が意図どおりなら -i で上書きします(後述)。
1行の中での置換と範囲指定
複数行の話に入る前に、よく組み合わせる基本形です。
# 各行の最初の1か所だけ置換
sed 's/foo/bar/' file.txt
# 各行のすべての一致を置換
sed 's/foo/bar/g' file.txt
# 10〜20行目だけを対象にする
sed '10,20s/old/new/g' config.conf
# BEGIN を含む行から END を含む行までを対象にする
sed '/BEGIN/,/END/s/debug/info/g' app.conf
# server で始まる行と、その後の5行を対象にする(GNU 拡張)
sed '/^server/,+5s/timeout 30/timeout 60/' site.conf
# パスを含むときは区切り文字を | などに変える
sed 's|/var/www/old|/var/www/new|g' site.conf
範囲指定は「行ごとに置換する対象を絞る」もので、改行をまたいだパターンには一致しません。改行を含むパターンは次の節の方法を使います。
連続する2行を置換する(N・P・D)
N は次の行をパターンスペースに読み足します(間には改行 \n が入ります)。ただし sed 'N;s/foo\nbar/X/' だけでは、1・2行目、3・4行目……と2行ずつ区切って読むため、foo が偶数行にあると一致しません。
printf 'x\nfoo\nbar\ny\nfoo\nbar\n' > sample.txt
sed 'N;s/foo\nbar/REPLACED/' sample.txt
# x / foo / bar / y / REPLACED ← 2〜3行目の foo/bar が置換されない
1行ずつずらしながら2行を見るには、$!N・P・D を組み合わせます。
sed '$!N;s/foo\nbar/REPLACED/;P;D' sample.txt
# x / REPLACED / y / REPLACED
$!N: 最終行以外で、次の行を読み足すP: パターンスペースの最初の改行までを出力するD: 最初の改行までを削除し、残りがあれば次の行を読まずに処理をやり直す
一致した行だけを出したいときは -n と p フラグを使います。
sed -n '$!N;s/foo\nbar/REPLACED/p' sample.txt
ファイル全体をまとめて置換する(-z または :a;N;$!ba)
3行以上にまたがるパターンや、行数が決まっていないパターンは、ファイル全体をパターンスペースに入れてから置換すると書きやすくなります。
GNU sed では -z を付けると、行の区切りが改行ではなく NUL 文字になります。通常のテキストファイルには NUL が無いので、ファイル全体が1つの「行」として扱われ、\n で改行に一致させられます。
sed -z 's/foo\nbar\nbaz/NEW/g' file.txt
-z が使えない環境では、ラベルとループで全行を読み込む書き方を使います。
sed ':a;N;$!ba;s/foo\nbar\nbaz/NEW/g' file.txt
:a: ラベル a を置くN: 次の行を読み足す$!ba: 最終行でなければラベル a に戻る
どちらもファイル全体をメモリに読み込むので、数 GB のログのような大きなファイルには向きません。また、bash で ! を含むコマンドを二重引用符で囲むと履歴展開されることがあるため、単一引用符で囲んでください。
置換後の文字列に改行を入れることもできます(GNU sed)。
echo 'a,b' | sed 's/,/\n/'
# a
# b
マーカーで囲まれたブロックを差し替える
設定ファイルで # BEGIN X と # END X に囲まれた部分を書き換える、という作業は、行の範囲指定で扱えます。次のファイルを例にします。
a
# BEGIN X
old1
old2
# END X
b
ブロックを削除する
sed '/^# BEGIN X/,/^# END X/d' config.txt
# a / b
ブロック全体を別のテキストに置き換える
範囲に c コマンドを使うと、範囲の行を削除して、指定したテキストを1回だけ出力します。複数行のテキストは、行末に \ を付けて続けます。
sed '/^# BEGIN X/,/^# END X/c\
# BEGIN X\
new1\
# END X' config.txt
マーカーは残して、中身だけを別ファイルの内容にする
差し替える内容が長いときは、別ファイル(ここでは new.txt)に書いておき、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: BEGIN の行の後にnew.txtの中身を出力する- 範囲内の BEGIN 行と END 行は
b(スクリプトの最後へ移る)で残し、それ以外をdで削除する
結果は次のとおりです。
a
# BEGIN X
new1
new2
# END X
b
ファイルを直接書き換える(-i)と複数ファイルへの適用
-i を付けると、結果を元のファイルに書き戻します。-i.bak のように拡張子を付けると、書き換え前のファイルが config.txt.bak として残ります。
sed -i.bak 's/old/new/g' config.txt
diff config.txt.bak config.txt
複数ファイルにまとめて適用するときは find と組み合わせます。先に grep -rl で対象のファイルを確かめてから実行してください。
# 対象のファイルを確認
grep -rl '127.0.0.1' /etc/app --include='*.conf'
# 確認したうえで書き換え(各ファイルに .bak が残る)
find /etc/app -name '*.conf' -exec sed -i.bak 's/127\.0\.0\.1/0.0.0.0/g' {} +
正規表現では . が任意の1文字に一致するため、IP アドレスやドメイン名を置換するときは \. とエスケープします。
置換のルールが多いときは、1行に1つずつ書いたスクリプトファイルを -f で読み込めます。
cat rules.sed
# s/foo/bar/g
# /^#/d
sed -f rules.sed input.txt > output.txt
GNU sed と macOS(BSD sed)の違い
| 項目 | GNU sed(Linux) | BSD sed(macOS) |
|---|---|---|
| バックアップなしで上書き | sed -i 's/a/b/' f |
sed -i '' 's/a/b/' f |
| バックアップ付きで上書き | sed -i.bak 's/a/b/' f |
sed -i .bak 's/a/b/' f または sed -i.bak |
-z(NUL 区切り) |
使える | GNU 拡張のため、使えない版がある |
addr,+N の範囲指定 |
使える | GNU 拡張のため、使えない版がある |
置換後の文字列の \n |
改行になる | 改行にならず、n などになる |
macOS で GNU sed と同じ書き方をしたい場合は、Homebrew の gnu-sed パッケージを入れると gsed コマンドとして使えます。
WordPress のデータには sed を使わない
WordPress のデータベースのダンプ(.sql)に対して sed でドメインを置換すると、シリアライズされたデータ(s:20:"..." のように文字数を持つ値)の文字数が合わなくなり、ウィジェットやテーマの設定が読み込めなくなることがあります。データベース内の URL の置換は、シリアライズを考慮する wp search-replace を使ってください。