sedで複数行にまたがった文字列を置換する方法

2023-02-09Bash

はじめに

sed コマンドで置換する場合、 複数行にまたがった文字列も置換対象としたい場合があります。

検証環境

$ uname -moi
x86_64 MacBookPro11,4 Darwin

$ bash -version | head -n 1
GNU bash, バージョン 5.0.11(1)-release (x86_64-apple-darwin18.6.0)

$ sed --version | head -n 1
sed (GNU sed) 4.2.2

置換対象の文字列が複数行にまたがる場合とは?

sed はラインエディタの思想を受け継いでいるため、 基本的に入力情報を1行ずつ受け取って処理を行 います。

例えば、 % で囲まれた部分を空文字に置換するといった場合、合致パターンが 1 行で収まるため容易に置換できます。

$ echo 'xx%aaa%yyy'
xx%aaa%yyy

$ echo 'xx%aaa%yyy' | sed 's/%.*%//'
xxyyy

ところが、 合致パターンが複数行にまたがったときにはうまく置換してくれません

# 2つの%の間に改行が含まれている
$ echo 'xxx%aaa
bbb%yyy'
xxx%aaa
bbb%yyy

# 置換されない
$ echo 'xxx%aaa
bbb%yyy' | sed 's/%.*%//'
xxx%aaa
bbb%yyy

2 つの対処法を取り上げます。

1.GNU 版 sed が利用できる場合

GNU 版の sed が利用できる場合、 -z オプションを利用することで、簡単に対処できます。
これは sed のデフォルトの挙動である「行単位の処理」から「ヌル文字単位の処理」に挙動を切り替える、というオプションになります。

注意しなければならないのは、GNU 版の sed でしか利用できないオプションである点です。Linux の場合は GNU 版の sed が導入されているディストリビューションが多いですが、Mac の場合にはプリインストールの sed では利用できません。GNU 版の sed を brew install しなければなりません

どうしても GNU 版の sed を利用できない場合については後述します

通常のテキストにヌル文字が入っていることはありませんので、これによりテキスト全体を読み込み、一度に置換できます。

$ echo 'xxx%aaa
bbb%yyy' | sed -z 's/%.*%//'
xxxyyy

2.GNU 版 sed が利用できない場合

GNU 版の sed が利用できないケースも当然あります。
その場合には、以下のコマンドをお試しください。

# ちゃんと置換されていますね!
$ echo 'xxx%aaa
bbb%yyy' | sed ':l; N; s/%.*%//; b l;'
xxxyyy

sed のコマンド部分のみ簡単に解説します。

今回取り上げたコマンドは ; を区切り文字として 4 つの処理で構成されています。

  1. :l
  2. N
  3. s/%.*%//
  4. b l

それぞれのフェーズで行っている処理は以下のようになります。

  • :goto 文におけるラベル のようなものを設定します。ここでは l というラベルを設定していることになります。
  • N は、1 行データを読み込んで sed のバッファ領域に蓄えます。
  • sed のバッファ領域を s 内部コマンドで置換します。
    • 置換ができた場合はバッファの内容が出力されます。
    • 置換ができなかった場合はバッファの内容は据え置きされます。
  • b l は、ラベル l に戻る命令になります。

最終的に残ったバッファ領域の内容は、処理終了直前に出力されます。

ひとこと

sed -z はかなり強力で、例えば「連続して並んだ空白行は 1 つにする」といった処理も簡単に実現できます。

# 空白行が多い
$ cat test.txt
    android
 
 
    Linux
    windows
 
 
 
 
    windows
 
    Linux

# スッキリ!
$ sed -z 's/\n\{3,\}/\n\n/g' < test.txt
    android

    Linux
    windows

    windows

    Linux

注意しないといけない点があります。
通常は行単位に実行される sed を全文字列を一度に処理するため、メモリ負荷が増えます。

あまりに巨大なファイルを処理する場合にはメモリ不足の注意が必要です。

2023-02-09Bash