概要
sedは、入力ストリームに対して様々なテキスト変換をおこなう、ストリームエディタです。
cut, grep, trといった基本的なフィルタコマンドと比較して、柔軟なテキスト処理が可能です。
このsedの機能の1つとして、パターンスペース・ホールドスペースがあります。
高度なテキスト処理が可能になる反面、パターンスペース・ホールドスペースは、動作が理解し辛いという難点があります。
ですが、sedのパターンスペース・ホールドスペースの動作を丁寧に解説した記事は、私が探した限りでは見つかりませんでした。
そこで、sedを深く学ぶ方への助けとして、また私自身の復習として、sedのパターンスペース・ホールドスペースの動作を、記事としてまとめました。
本記事では、sedのパターンスペース・ホールドスペースの動作を、図示して解説します。
実行環境
- Arch Linux 4.8.8-2-ARCH
- GNU bash 4.4.5
- GNU coreutils 8.25-2
- sed (GNU sed) 4.2.2
シェル芸でのみ使用
- GNU findutils 4.6.0-2
- grep (GNU grep) 2.26
- gawk 4.1.4
- nkf 2.1.4
- mecab 0.996
解説
前提知識の解説
まず、本記事を読むにあたり、前提知識となるsedの動作を、ホールドスペース抜きで解説します。
sedは、各レコードをパターンスペースに取り込み、パターンスペース(またはホールドスペース)に対して各コマンドを順次実行します。
この際、後に実行するコマンドは、先に実行したコマンドの処理結果の影響を受けます。
正確には、前述の通り、 パターン(ホールド)スペースの内容に対して、各コマンドの処理が順次実行 されます。
以下、一例を示します。
※本記事は、sedの基本的な使い方を知っている方を、対象としています。
したがって、sedの各コマンドの意味や、ホールド・パターンスペースとは何かについては、本記事では解説しません。
必要に応じて、man sedや、書籍『sed & awkプログラミング 改訂版』を参照してください。
$ cat poem
Hey Siri, show me the magic!
I'm afraid I cannot do it.
$ cat poem | sed 's/Siri/Ketu/; /not/s/.*/Sure./; /Sure/aただし魔法は尻から出る'
Hey Ketu, show me the magic!
Sure.
ただし魔法は尻から出る
上記の処理を、各レコード・各コマンドについて、それぞれ説明します。
まず、1レコード目の入力、Hey Siri, show me the magic!を、sedはパターンスペースに取り込みます。
レコードをパターンスペースに取り込んだ後、パターンスペースに対してコマンドを順次実行していきます。
初めに、1番目のsコマンドを実行します。
その結果、パターンスペースのSiriという文字列が、Ketuに置換されます。
次に、2番目のsコマンド、最後のaコマンドが適用されます。
ただし、それぞれアドレスに正規表現/not/、/Sure/が指定されているため、現在のパターンスペースにはマッチせず、今回はどちらも実行されません。
全コマンドの適用が終わった後は、sedのデフォルトの動作として、パターンスペースの内容が自動的に出力されます。
今回の場合、s/Siri/Ketu/が実行された結果が、出力されます。
次に、2レコード目の入力、I'm afraid I cannot do it.を、パターンスペースに取り込みます。
レコードをパターンスペースに取り込んだ後は、1レコード目と同様に、コマンドを順次実行していきます。
まず、1番目のsコマンドを実行します。
ただし、現在のパターンスペースは正規表現/Siri/にマッチしないため、結果的に何も処理がおこなわれません。
次に、2番目のsコマンドを実行します。
今回、パターンスペースに文字列notが含まれているため、2番目のsコマンドは実行され、パターンスペースの内容はSure.に置換されます。
そして、3番目のコマンドaのアドレス、正規表現/Sure/にマッチするかどうかを判定します。
ここで、判別対象は、 2番目のsコマンドが実行された後のパターンスペース が用いられます。
したがって、パターンスペースSure.に正規表現/Sure/にマッチし、3番目のaコマンドが実行されます。
その結果、パターンスペースにただし魔法は尻から出るが追記されます。
そして最後に、1レコード目と同様に、全コマンド実行後のパターンスペースが出力されます。
上記の処理過程の結果、次の内容が出力されます。
Hey Ketu, show me the magic!
Sure.
ただし魔法は尻から出る
以上が、前提知識となるsedの基本動作になります。
ここで、繰り返しになりますが、 sedの各コマンドは、パターンスペースに対して順次実行される 事に、留意してください。
次に、2つの実用的なsedスクリプトを用いて、ホールドスペースを含めた動作を解説します。
例題1
Linuxのtacや、BSDのtail -rのように、入力レコードを反転して出力するスクリプトを用いて、パターンスペース・ホールドスペースの動作を解説します。
以下、入力データとスクリプトの実行例を示します。
$ cat shells
シェル変態
シェル戦隊
シェル軟体
シェル倦怠
$ cat shells | sed '1!G; $!h; $!d'
シェル倦怠
シェル軟体
シェル戦隊
シェル変態
上記の処理を、各レコード・各コマンドについて、それぞれ説明します。
まず、前セクションの例と同様に、1行目の入力レコードシェル変態を、パターンスペースへと取り込みます。
次に、1番目のGコマンドを適用し、ホールドスペースの内容を、パターンスペースへと追記します。
ただし、Gコマンドのアドレスは1行目以外1!に指定されているため、今回は実行しません。
(アドレス1!を指定せずに実行した場合、パターンスペースに空行が追記されます。)
その後、2番目のhコマンドを実行し、現在のパターンスペースの内容シェル変態を、ホールドスペースへとコピーします。
そして、3番目のdコマンドを実行し、パターンスペースの内容を削除します。
ここで、ホールドスペースの内容は削除されない事に、着目してください。
最後に、全コマンドを実行した状態のパターンスペースが、sedの自動出力機能によって出力されます。
ただし、 3番目のdコマンドによってパターンスペースの内容は削除されているため、ここでは何も出力されません 。
次に、2行目の入力レコードシェル戦隊を、処理していきます。
1行目と同様に、まず入力レコードをパターンスペースへと取り込みます。
次に、1番目のGコマンドを実行し、ホールドスペースの内容シェル変態を、パターンスペースへと追記します。
今回は、2行目であるため、アドレス1!に該当し、Gコマンドは実行されます。
その結果、パターンスペースの内容は、シェル戦隊\nシェル変態になります。
その後、2番目のhコマンドを実行し、現在のパターンスペースの内容シェル戦隊\nシェル変態を、ホールドスペースへとコピーします。
ここで、 直前のGコマンドの処理によって変化したパターンスペースを用いる ことに、留意してください。
また、1行目の処理でホールドスペースにコピーした内容シェル変態は、上書きされます。
(hの代わりにHコマンドを使うと、上書きコピーではなく追記になります。)
そして、3番目のdコマンドを実行し、パターンスペースの内容を削除します。
最後に、全コマンドを実行した状態のパターンスペースが、sedの自動出力機能によって出力されます。
ただし、3番目のdコマンドによってパターンスペースの内容は削除されているため、今回も何も出力されません。
そして、3レコード目のシェル軟体の処理をおこないます。
この3レコード目の動作は、2レコード目の動作と同様であるため、詳細な解説は省きます。
最後に、末尾行である4レコード目を処理します。
まず、入力レコードシェル倦怠を、パターンスペースへと取り込みます。
次に、1番目のGコマンドを実行し、ホールドスペースの内容シェル倦怠\nシェル軟体\nシェル戦隊を、パターンスペースへと追記します。
そして、2・3番目のhとdコマンドを適用します。
ただし、両方共アドレスが末尾行以外$!に指定されているため、今回は実行しません。
最後に、全コマンドを実行した状態のパターンスペースシェル倦怠\nシェル軟体\nシェル戦隊\nシェル変態が、sedの自動出力機能によって出力されます。
以上が、tacやtail -rと同等の動作、sedを用いた入力レコードの反転処理になります。
例題2
今度は、入力レコードを縦方向に対称に出力するスクリプトを用いて、パターンスペース・ホールドスペースの動作を再度解説します。
以下、入力データとスクリプトの実行例を示します。
$ cat saga
よいかジェラール
われわれはインペリアルクロス
という陣形で戦う
$ cat saga | sed '$!p; 1!G; $!h; $!d'
よいかジェラール
われわれはインペリアルクロス
という陣形で戦う
われわれはインペリアルクロス
よいかジェラール
上記の処理を、各レコード・各コマンドについて、それぞれ説明します。
まず、これまでの例と同様に、1行目の入力レコードよいかジェラールを、パターンスペースへと取り込みます。
次に、1番目のpコマンドを実行し、現在のパターンスペースの内容よいかジェラールを出力します。
その後、2番目のGコマンドを適用します。
ただし、アドレスに1行目以外1!が指定されているため、今回はGコマンドは実行されません。
そして、3番目のhコマンドを実行し、パターンスペースの内容よいかジェラールを、ホールドスペースへとコピーします。
また、4番目のdコマンドを実行し、パターンスペースの内容を削除します。
最後に、全コマンドを実行した状態のパターンスペースが、sedの自動出力機能によって出力されます。
ただし、直前のdコマンドによってパターンスペースの内容は削除されているため、ここでは何も出力されません。
次に、2行目の入力レコードわれわれはインペリアルクロスを、処理していきます。
まず、1行目と同様に、入力レコードをパターンスペースへと取り込みます。
次に、1番目のpコマンドを実行し、現在のパターンスペースの内容われわれはインペリアルクロスを出力します。
その後、2番目のGコマンドを実行します。
1行目では実行されませんでしたが、今回はアドレス1!にマッチするため、ホールドスペースの内容よいかジェラールが、パターンスペースに追記されます。
その結果、パターンスペースの内容は、われわれはインペリアルクロス\nよいかジェラールになります。
そして、3番目のhコマンドを実行し、現在のパターンスペースの内容われわれはインペリアルクロス\nよいかジェラールを、ホールドスペースへとコピーします。
また、4番目のdコマンドを実行し、パターンスペースの内容を削除します。
最後に、全コマンドを実行した状態のパターンスペースが、sedの自動出力機能によって出力されます。
ただし、1行目と同様に、直前のdコマンドによってパターンスペースの内容は削除されているため、ここでは何も出力されません。
最後に、末尾行の入力レコードという陣形で戦うを処理します。
まず、これまでと同じく、入力レコードをパターンスペースへと取り込みます。
次に、1番目のpコマンドを適用します。
ただし、今回は末尾行であるため、アドレス$!にマッチせず、pコマンドは実行されません。
その後、2番目のGコマンドを実行します。
現在のホールドスペースの内容われわれはインペリアルクロス\nよいかジェラールを、パターンスペースという陣形で戦うに追記します。
その結果、パターンスペースの内容は、という陣形で戦う\nわれわれはインペリアルクロス\nよいかジェラールになります。
そして、3・4番目のhとdコマンドを適用します。
しかし、1番目pコマンドと同様に、アドレス$!にマッチしないため、コマンドはどちらも実行されません。
最後に、全コマンドを実行した状態のパターンスペースが、sedの自動出力機能によって出力されます。
今回は、直前のdコマンドが実行されなかったため、パターンスペースの内容は空ではありません。
その為、現在のパターンスペースの内容、という陣形で戦う\nわれわれはインペリアルクロス\nよいかジェラールが、出力されます。
以上が、入力レコードを縦方向に対称に出力する動作になります。
1文で簡単に書き表すと、「入力nレコードに対し、n-1レコード目までをpコマンドで出力しつつ、入力を行単位で反転しながらホールドスペースにて保持、n行目でホールドスペースを出力する」という処理になります。
まとめ
以下、例示で強調したポイントをまとめます。
-
sedの各コマンドは、先頭から順次実行される。 -
sedの各コマンドは、前に実行したコマンドの処理結果の影響を受ける。- 正確には、前コマンドの処理が適用されたパターン・ホールドスペースを用いて、処理をおこなう。
-
sedのデフォルトの動作であるパターンスペースの自動出力も、同様に影響を受ける。
これらの点に留意すれば、パターン・ホールドスペースを用いた複雑なsedスクリプトでも、理解しやすくなると思います。
補足事項
(2017-02-13追記)
sed は、入力をレコード単位で処理しますが、パターン・ホールドスペース内では、レコードの概念はありません。
言い換えると、改行文字は、出力するまで文字 \n として扱われます。
つまり、パターンスペース内に含まれる改行文字を、 s コマンドで置換する事が可能です。
以下に例を示します。
$ seq 1 4
1
2
3
4
$ seq 1 4 | sed '1!G; $!h; s/\n/ /g; $!d' # 入力を逆順にしつつ、各行をスペース区切りの1行として出力する
4 3 2 1
(図解部分では、可読性を優先したため、改行した状態で記載しました。)
参考にしたもの
$ man sed$ info sed- 『sed & awkプログラミング 改訂版』、Dale Dougherty, Arnold Robbins 著、福崎 俊博 訳、オライリー・ジャパン
雑記
-
sedのパターンスペース・ホールドスペースの概念は、動作を把握しづらくて、なかなか難しい。- とはいえ、本記事のように、1レコード毎、1コマンド毎に動作を書いてみると、理解しやすいと思う。
- 紙やホワイトボードに書いてみて、実際に手を動かしてみる事が大切。
- ホールドスペースは、Windowsのクリップボードの仕組みに似ているかも?
- Windows環境にてメモ帳を起動して、メモ帳をパターンスペース、クリップボードをホールドスペースに見立てて、動作を確認してみるのも、良い勉強法かもしれない。
-
Hコマンドのような、ホールドスペースに追記する処理は、残念ながら再現できないけれど...
- 実は、例1と例2のスクリプトは、コード自体は殆ど同じだったりする。
- 少しコマンドを弄っただけで、動作が大きく変化する所が、
sedの面白い部分であり、難しい所でもあると思う。
- 少しコマンドを弄っただけで、動作が大きく変化する所が、
- 今回、
sedの動作を、表形式にして書き表してみたけれど...- ホールドスペースの初期値は何か、パターンスペースの内容はどの段階で上書きされるのかといった詳細な動作は、実はきちんと理解していなかったり。
- パターンスペースが自動出力される処理に関しては、今回記事を書くにあたり必要になったので、
info sedやGNUsedのソースを読んで何とか把握した。 -
sedの内部動作に詳しい方がいらっしゃいましたら、本記事の些細な間違い等を指摘していただけますと、幸いですm(_ _)m
- 例題2の
sedスクリプトを使えば、シェル上で回文を作れます。
01 #!/bin/sh
02
03 echo たけやぶ | # 原文の出力
04 grep -o . | # 縦にする
05 sed '$!p; 1!G; $!h; $!d' | # 入力レコードを鏡合わせで出力
06 xargs | # 横にする
07 tr -d ' ' # 余分なスペースの除去
以下、コピペ用のワンライナー版。
$ echo たけやぶ | grep -o . | sed '$!p; 1!G; $!h; $!d' | xargs | tr -d ' '
- 漢字が含まれていても、いい感じに出来ます♥
01 #!/bin/sh
02
03 echo 酢で漬けた | # 原文の出力
04 mecab | # 入力を単語単位に分解
05 awk -F, '!/EOS/ && $0=$NF' | # 余分なレコード・フィールドの除去
06 grep -o . | # 1文字1レコードに変換
07 sed '$!p; 1!G; $!h; $!d' | # 入力レコードを鏡合わせで出力
08 xargs | # 1レコードn文字に変換
09 tr -d ' ' | # 余分なスペースの除去
10 nkf --hiragana # カタカナをひらがなに変換
以下、コピペ用のワンライナー版と、その出力結果。
$ echo 酢で漬けた | mecab | awk -F, '!/EOS/ && $0=$NF' | grep -o . | sed '$!p; 1!G; $!h; $!d' | xargs | tr -d ' ' | nkf --hiragana
すでつけたけつです
$ !! | mecab
echo 酢で漬けた | mecab | awk -F, '!/EOS/ && $0=$NF' | grep -o . | sed '$!p; 1!G; $!h; $!d' | xargs | tr -d ' ' | nkf --hiragana | mecab
す 名詞,一般,*,*,*,*,す,ス,ス
で 助詞,格助詞,一般,*,*,*,で,デ,デ
つけ 動詞,自立,*,*,一段,連用形,つける,ツケ,ツケ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
けつ 名詞,一般,*,*,*,*,けつ,ケツ,ケツ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
- (CLI(パイプライン)上でかな漢字変換をする方法は、さすがに分からなかった...)
-
uim-fepのあたりを使って、上手いこと出来ないのかな? - ご存知の方がいらっしゃいましたら、ご教授くださいますとありがたいです。
-
- スッゲーどうでもいい話。
- 尻だのケツだの書いていたら、半ケツ状態になりました。
うっ... 電源ケーブルをコンセントに挿そうとしゃがんだら、スーツの股間のあたりから、ブチッっていう音がした...... もしかしたら、また半ケツ状態かも(`;ω;´)
— ginjiro (@gin_135) 2016年11月28日
駅のホームでやたらと尻がスースーすると思ったら、案の定半ケツになってた(T_T) やっぱり、朝のあれが原因だよなぁ... てか、客先にいる間はずっと、パンツ見えている状態だったのかorz このスーツもうダメだ...
— ginjiro (@gin_135) 2016年11月28日








































