【Sed活用講座】 パターンスペースとホールドスペースの使い方を理解する


2021/12/11
蛸壺の技術ブログ|パターンスペースとホールドスペースの使い方を理解する

皆さんはSedコマンドをどのくらい使われていますでしょうか。

通常だと
sed -e 's/hoge/piyo/'のような文字列の置換くらいは良く活用されていると想像します。

しかし、Sedコマンドの真髄は、
パターンスペースホールドスペースを理解して、これを駆使することにあります。

今回はこのパターンスペースとホールドスペースの概念と基礎的な使い道を解説していきます。

なお、Sedで
改行を扱う場合は非常にデリケートに処理する必要があります。以前、sedで改行をどうするかを考察した記事も用意しましたので、興味があればご覧ください。


ウェブサイト型のBashエミュレータを利用する

シェルコマンドは何かOSのターミナルソフトで動かす、というスタイルでも良いのですが、最近ではとても簡単に基礎的なシェルコマンドをオンラインかつ無償で試せるようになっています。

いくつかシェルコマンドを試せるサイトが存在しますが、例えば以下のBashオンラインエディタを利用させてもらいましょう。

Bash online editor, IDE, compiler, interpreter, and REPL

このサイトではもちろんSedコマンドもオンラインで試すことが可能です。

とりあえずどのバージョンのsedが動いているかも確認しておくと、

合同会社タコスキングダム|蛸壺の技術ブログ

で、
GNU sed v4.4がバックエンドで動作していることが分かります。

以前の
ブログの内容でも取り上げたように、何種類かあるsedによっては同じコードでも若干挙動が違ったり、エスケープシークエンスの使い方が異なったりします。

このようなウェブサイト形のプレイグラウンドはブラウザさえ有れば何処でも誰でも扱うことができるので、sedの違いを考慮せずとも、コードが同一で有れば誰がやっても同じ結果が返ってきます。

同じ開発チームで、他のエンジニアと技術的な情報共有のツールとしても便利に使えますので積極的に使ってみてください。


パターンスペースとは何かを理解しよう

では本題に移りましょう。

まずは
パターンスペースの概念から説明していきます。

最初に以下のスクリプトを試してみます。

            
            $ sed -r '' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇実行結果
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
        
このsedには処理は''と空にしているため、これは「何もしない」スクリプトになってます。

ですが何もしないと言っておきながら、実際には入力したドキュメントが素通りで全て標準出力されてしまっています。

Sedの動作の大前提として、読み込んだ行ストリームごとに
「パターンスペース」という入れ物のように使えるメモリ空間にコピーされ、行が一つ一つ読み込まれる度にパターンスペースの中のテキストの内容が更新されていきます。

合同会社タコスキングダム|蛸壺の技術ブログ

ですので、先程の
何もしないといったSedスクリプトでも、実はパターンスペースに入れられた内容をその都度標準出力するpコマンドが暗黙的ルールで動作しています。

なので、本来の意味で
「何もしない」ことをsedにさせたいなら、このデフォルトでパターンスペースの内容を標準出力する機能を抑制する必要があります。

それを行うのが、sedの
-nオプションです。

            
            $ sed -n -r '' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇結果は何も出力されない
        
これで暗黙的に付けられていたpコマンドが抑制され、勝手にパターンスペースの内容は表示されなくなりました。

ここで改めて、明示にパターンスペースの内容を表示させると以下のスクリプトを使います。

            
            $ sed -n -r 'p' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇実行結果
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
        
と、なんだか同じことを回りくどく説明しましたが、パターンスペースを駆使したsedスクリプトのテクニックにはこの-n (--quiet, --silent)オプションの利用が欠かせません。

この
-nオプションの有り無しでsedスクリプトの応用の幅が広がり、より柔軟な処理が可能になります。

まさに例えるならAT車にクラッチペダルが追加されて、MT車のように多彩なギアチェンジが可能になる...ような(?)ものです。

どう柔軟かというと、先程のスクリプトで、3、4、7、10行目の内容だけを表示させたい場合、

            
            $ sed -n -r '
    3p
    4p
    7p
    10p
' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇実行結果
3行目
4行目
7行目
10行目
        
と行数を指定してあげると良いだけになります。

裏でパターンスペースの存在を意識しながらでなければ、sedがなぜ出力できるか、という基礎的なところが理解できないことになりますので、sedを深く使いこなしたい場合にはこの
-nオプションの違いをしっかり覚えておきましょう。

sed初心者あるある〜二重に出力されてしまう

パターンスペースを理解しないまま勢いだけでsedを使ってしまうと、必ず躓いてしまうのが、「sedが勝手に余計な出力をしてくる」問題かと思います。

            
            $ sed -r '
    3p
    4p
    7p
    10p
' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇実行結果
1行目
2行目
3行目
3行目
4行目
4行目
5行目
6行目
7行目
7行目
8行目
9行目
10行目
10行目
        
と、こんな感じにちゃんと指定した行だけpしてるつもりでも、全ての行が表示されて困ってしまうことがあります。

既に上記でその理由を示した通り、
-nオプションが使えてないのが原因です。

つまり「パターンスペース」とは

ということで上記の話をまとめると、パターンスペースには、

            
            + パターンスペースとは、入力された行の一時保存場所である
+ Sedのスクリプトが適用されるのは、
    パターンスペースに置かれた行に対してである
+ パターンスペースに置かれた一行分がスクリプトに処理され、
    順次各行ごとに処理が実行される、というサイクルを繰り返す
+ パターンスペースは原則一行づつ読み込まれ、
    パターンスペースに置かれた古い行は上書きされる
+ 複数のSedスクリプトが指定されている場合、パターンスペースに置かれた
    行でスクリプト順に逐次適用される
+ Sedの処理は、デフォルトでパターンスペースが順次標準出力される
        
という特徴があることが分かります。


ホールドスペースとは何かを理解しよう

簡単なsedコマンドの利用程度では、ほぼ活躍する場面はないのですが、sedの隠されたもう一つのデータ保管領域である『ホールドスペース』にも触れておきましょう。

先ほど説明していたパターンスペースは、新しい行が読み込まれるとその前に読み込まれていた行のデータは上書きされて消えてしまいます。

でも場合によっては複雑なテキスト操作をしたい場合、現在読み込んでいる行より前のデータを何処かに保管しておいて、後で上手い具合に使いたい、ということも考えなければなりません。

そんな時に使うのが、
「ホールドスペース」というパターンスペースを補助するための仕組みです。

ホールドスペースは利用者が使わないと普段は空のままで、特に何もせずにじっとしていますが、常にスタンバイ状態にあり、使おうと思うといつでも呼び出すことができます。

またホールドスペースにあるテキストは直接操作はできませんが、好きなタイミングでパターンスペースからテキストデータを盗んだり、逆に保持している内容を押し付けたりと、多彩な操作を行うことができます。

合同会社タコスキングダム|蛸壺の技術ブログ

この操作はあくまでもパターンスペースとホールドスペースの間のデータのやり取りを制御に限られますが、以下のSedスクリプトコマンドが使えます。

コマンドシンボル

作用

x

パターンスペースとホールドスペースのテキストを交換

h

パターンスペースのテキストでホールドスペースを置換

H

パターンスペースのテキストをホールドスペースの末尾へ改行+追加

g

ホールドスペースのテキストでパターンスペースを置換

G

ホールドスペースのテキストをパターンスペースの末尾へ改行+追加

この5つの操作が基本となりホールドスペースを一時的なテキストの避難場所として、複雑なテキスト操作が行えるようになります。

ホールドスペースを使った事例

ホールドスペースを利用した例を挙げると、ベタなところでテキストの行を逆順にすることが簡単にできます。

            
            $ sed -n -r '
    1!G # 1行目以外ならホールドスペース > パターンスペースへ末尾追加
    h   # 全ての行でパターンスペース > ホールドスペースで置換
    $p  # 最後の行ならパターンスペースを標準出力
' <<EOF
1行目
2行目
3行目
4行目
5行目
6行目
7行目
8行目
9行目
10行目
EOF
#👇実行結果
10行目
9行目
8行目
7行目
6行目
5行目
4行目
3行目
2行目
1行目
        

もう少し実用的な例として、最近著者がホールドスペースを使った手頃な例で、jsコードの関数の中身の書き換えもsedスクリプトで行うといざという時、処理を自動化できるようにできます。

            
            $ func_name='getAssetGetConfig \(asset\)'
$ sed -n -r '
#👇書き換える関数をループ処理で修正
/'"$func_name"'/ {
    :loop
    /\}$/! {
        N
        #👇出力される文字列で要らない部分を削除
        s-internalapi/asset/--
        s-/get/--
        b loop
    }
    p
}
#👇書き換える関数以外は通常の標準出力
/'"$func_name"'/!p
' <<EOF
    setAssetHost (assetHost) {
        this.assetHost = assetHost;
    }
    getAssetGetConfig (asset) {
        return \`\${this.assetHost}/internalapi/asset/\${asset.assetId}.\${asset.dataFormat}/get/\`;
    }
EOF
#👇出力結果
#👇関数の中身を期待通りに書き換えることが出来る
    setAssetHost (assetHost) {
        this.assetHost = assetHost;
    }
    getAssetGetConfig (asset) {
        return `${this.assetHost}/${asset.assetId}.${asset.dataFormat}`;
    }
        
ここではもっと高度なテクニックのラベルループや次の行を先読みするNコマンドなども使っています。この辺のテクニックはまた別の機会にじっくり解説していく予定です。

つまり「ホールドスペース」とは

では最後にホールドスペースの要点だけをまとめてみます。

            
            + パターンスペースに対して独立した保存領域を持っている
+ 反復処理を伴うような複雑な処理に利用できる
+ パターンスペースの行の読込みサイクルにも関係なく、
    Sed処理中はずっとテキストの内容を保持できる
+ パターンスペースとの間でデータを柔軟にやり取りできる
+ 巨大なテキストも丸ごと保持はできるが、
    すべての入力してしまうとメモリを圧迫し、
    処理パフォーマンスが低下する恐れがあることに注意
        
実際にはもっと細かいルールもありますが、全部挙げると切がないですので、後はご自分でスクリプトを打ち込みながら慣れていってください。


まとめ

今回は、Sedをよりデープに使いこなすための「パターンスペース」「ホールドスペース」の概論的な話をまとめていきました。

この2つをしっかり理解していることで、Sedの応用は無限大に広がります。

是非とも自分の手でカタカタとスクリプトを書いて実行してみながら、Sedがどうテキストを処理しているのか確認してみてください。

参考サイト

sed|memo.open-code.club

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。