<!doctype linuxdoctr system>

<article>

<title>SED教室
<author>仙石 浩明 @ 川崎市, <tt>sengoku@gcd.org</tt>
<date>Release 1.11 (平成七年秋)
<trans>遠藤 康裕, <tt>quiver@tky3.3web.ne.jp</tt> (SGML版)
<tdate>平成十一年五月二十六日

<abstract>
SED は、UNIX, MS-DOS など多くの OS 上で利用可能な非会話型ストリームエ
ディタです。高機能である反面、スクリプトが慣れるまでは難しいので、あまり
使われていませんでした。そこで少しでも多くの方に SED のすばらしさを知っ
ていただきたいと思い、SED 教室を Nifty Serve FMCN フォーラム 17 番会議室
で開講しました。その第一回目から第十三回目までの内容に加筆訂正を行ったも
のがこのパッケージです。
</abstract>

  <p>
  Release 1.00 (平成四年夏)    SED 教室 第一回 〜 第十一回
<!-- Release 1.00 (平成四年夏) <ref id="whatssed" name="「SEDって何？」"> -->
  <newline>
  Release 1.11 (平成七年秋)    SED 教室 第一回 〜 第十三回
  <newline>

<toc>



<sect>SED 教室 第一回 「SED って何？」<label id="whatssed">
<p>

  みなさんはプログラムを書いた事がありますか？最近はプログラムを書かない
人の方が圧倒的に多いですから、否定的な答ばかり返ってくるかも知れません。
たしかにプログラムを書くのって面倒ですよね。Nifty などのパソコン通信で便
利なフリーウェアがたくさん出回っているから、わざわざ自分で書く事はないと
お考えの方も沢山いらっしゃると思います。

  でも人の作ったプログラムって自分の要求に合わない事が多いんですよね。私
は Nifty に login するときは料金がもったいないので大抵オートパイロットで
すが、フリーウェアとして出回っているオートパイロット用のマクロは汎用的す
ぎてサイズが大きすぎるので自分で作ったマクロを使っています。言うのを忘れ
ていましたが私は普段 Dynabook J-3100SS001 (元祖ダイナです。) 使っていま
す。これの RAM ディスクだけで、通信から文書作成、C でのプログラミング、全
てを済まそうとしているのでフリーウェアをどんどん詰め込む訳にはいかないの
です。ハードディスクを持っている方ならば便利そうなフリーウェアを全部イン
ストールする事も可能でしょうが...

  それなら全部自分でアセンブラで書けば、ディスクの使用量は最小になるっ！
なんて書くとみなさん逃げ出すでしょうね。私も逃げます。^_^;) C で書くのも
面倒ですよね。BASIC ? 昔使っていましたが、今は処理系すら手元にはありませ
ん。前置きが長くなりすぎましたが、ここで SED の出番となるわけです。あな
たのディスクの中には SED.EXE はありますか？なければすぐインストールしま
しょう。SED は色々なことができます。head, tail, cut, paste, cat, grep, 
tr これらのツールとほとんど同様のことが、ちょっとスクリプトを書くだけで
できるのです。

  さてさて、いきなりわからない言葉が沢山出てきたからといって逃げ出さない
でくださいね。head を始めとするツールを一つも知らないからといって自分に
は縁のない世界だと思わないでください。これらのツールはなくてもコンピュー
タは使えますが、あると今まで大変手間がかかっていた作業が全部自動化されて
しまうかも知れません。エディタで置換という操作がありますね。この置換とい
う操作を知らなくてもエディタを使うことはできます。でも知ってたら相当手間
が省けます。これらのツールも置換操作と同様、知ってると大変便利なものなの
です。さらに SED はこれらのツールの親玉なのです。ということは SED を知っ
ているとすごく便利なのです。:-)

  あ、もう一つ見慣れない言葉がありましたか。スクリプト。辞書でみると 
script:手書き文字、とあります。SED はいろんなことができますから、何をさ
せたいか指定する必要があるのです。プログラムやマクロと似たようなものです
が、「手書き」という説明から、さっさと書けるという印象を受けませんでしたか？ 
そうです。たとえば会議室のログから ID とハンドル名を抜き出したい場合は、
コマンドラインから次のように SED を実行します。

<code>
A> sed -n -e "s/^[0-9][0-9][0-9]\/...   \(.*\)  .*/\1/p" logfile
</code>

  -e の次の「&dquot;」で囲まれた文字列が SED のスクリプトです。
そして「logfile」
は会議室のログのファイル名です。これと同じ事をするプログラムを C で書こ
うとすると、どんなに C のエキスパートでもデバッグ時間込みで 30 分はかか
ると思いますが、私が SED の命令を書くのに要した時間は 30 秒程度だったで
しょう。いっぱつで動作してしまったのでデバッグ時間はありません。スクリプ
トはこの例で言うと -e オプションの後に続く
&dquot;...&dquot; で囲まれた文字列です。
なにやら暗号めいていますが、たしかに書くだけなら 30 秒もかからないという
のがおわかりでしょう。慣れればこの程度のスクリプトなら特に考えずにすらす
らと書けます。

  SED のスクリプトは、このようにオプションとして指定することもできますが、
長くなる場合はファイルに書きます。この場合でしたら

<it>HANDLE.SED</it>
<code>
s/^[0-9][0-9][0-9]\/...   \(.*\)  .*/\1/p
</code>


  としておいて
<code>
A> sed -n -f HANDLE.SED logfile
</code>

  と呼び出します。スクリプトが書かれたファイルの拡張子は何でも良いのです
が、&dquot;.sed&dquot; を使うのが MS-DOS の世界では一般的なようです。

  ずいぶん長くなってしまったので今日はこの辺でおしまいということにしたい
と思います。SED.EXE を download して、第二回目の SED 教室をお楽しみに。
<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第二回 「フィルタ」<label id="filter">
<p>
  標準入力からテキストを読み込んで、適当に加工して標準出力に吐き出すプロ
グラムを総称してフィルタと呼びます。第一回目で列挙したツール head, tail,
 cut, grep, tr これらは全てフィルタです。それぞれの働きを簡単に紹介しま
すと、
<descrip>
<tag>head -5</tag>先頭の 5 行を出力する。
<tag>tail -5</tag>最後の 5 行を出力する。
<tag>cut -f2</tag>各行の第 2 フィールドのみ出力する。
フィールドとは空白文字で区切られた文字列の事です。
<tag>grep abc</tag>abc という文字列を含む行のみ出力する。
<tag>tr A-Z a-z</tag>A から Z までの文字を a から z までの文字に変換して
 (つまり大文字を小文字に変換して) 出力する。
</descrip>
  となります。これらのコマンドは沢山のオプションが指定できますが、とりあ
えず基本的なものを述べてみました。

  さて、ご存知の方も沢山いらっしゃるでしょうが、SED は Stream EDitor の
略です。Stream とは何ぞやというと、一般的にいえばシーケンシャルなデータ
列の事ですが、この場合は標準入力から読み込んだ文字列の事です。その 
Stream を適当に加工して (Edit) 標準出力に吐き出すのが SED の役目という事
になります。そして加工方法を指定するのがスクリプトです。

  SED はスクリプトを書く事によって様々なフィルタの働きを真似する事ができ
ます。論より証拠、前述したフィルタの真似をさせてみましょう。


<verb>
head -5         sed -e 5q
</verb>
これは -e を省略して sed 5q と書く事もできます。


<verb>
tail -5         sed -e :l -e $q -e N -e "2,5b l" -e D
</verb>
うーむいきなり長い。このように長いのはスクリプトを別に用意した方がよいかも知れません。

<it>TAIL.SED</it>
<code>
:l
$q
N
2,5b l
D
</code>
  というファイルを作っておいて、 sed -f TAIL.SED  と呼び出します。
<verb>
cut -f2         sed -e "s/^[^ ]+[ ]+\([^ ]+\) .*$/\1/"
</verb>
  このようにスクリプト内に空白文字などを含む場合は
&dquot;...&dquot; で囲む必要があります。
スクリプトをファイルに書くときは
&dquot;...&dquot; で囲む必要はありません。
<verb>
grep abc        sed -e /abc/!d
</verb>
こういう短いのっていいですね。
<verb>
tr A-Z a-z      sed -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
</verb>
  これは汚い。^_^;) tr みたいに A-Z と指定できると良かったのですが、sed 
ではこのように全部書かないと駄目です。こんな長いのいちいち入力する訳には
行きませんが、バッチファイルに書く分には問題ないでしょう。

  このように、大抵のフィルタの機能は真似できるので、ディスクの中に SED 
があればいくつかのプログラムをお払い箱にできてディスク容量の節約となりま
す。

  実際に SED を実行してどのような動きをするかぜひ実験してみてください。

  最後にスクリプトの文法をちょっとだけ紹介して終わりにしたいと思います。

  SED のスクリプトは、「実行条件」と「命令」からなります。例えば head -5
 の真似をする sed -e 5q の場合でしたら 5 が実行条件で q が命令です。5 が
なんで条件なのという声が聞こえてきそうですが、これは標準入力から読み込ん
だテキストの「5 行目ならば」という意味です。では命令 q の意味は？ 何とな
くわかりそうですね。quit の q でして、実行終了という事です。

  SED は一行づつ標準入力からテキストを読み込みまして、一行目、二行目と必
死 (かどうかは知りませんが :-) に数えます。そして一行読み込む度にスクリ
プトを眺めまして、実行条件が成立する物がもしあれば実行条件の後ろに書いて
ある命令を実行するのです。どの実行条件も成立しないときは標準入力から読ん
だテキストをそのまま標準出力に書き出します。かくして五行読み込んだ時点で
条件成立し、実行終了となります。実行終了の時も読み込んだ行を出力しますか
ら、合計 5 行が標準出力から出てくるという事になります。

  スクリプトの説明をちょっとだけしてみました。おわかりになりましたでしょ
うか。次回からスクリプトの文法をばんばん説明していきます。
<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第三回 「スクリプト」
<p>
  前回スクリプトの文法をちょっと紹介しましたが、復習を兼ねてもう一度今度
はもっと詳しく説明したいと思います。再び head -5 の真似をする SED スクリ
プトです。

<it>HEAD.SED</it>
<code>
5q
p
d
</code>

  このスクリプトの使い方は sed -f HEAD.SED とするんでしたね。もちろん -e 
オプションを使ってコマンドラインに書くこともできますが、こっちの方が見や
すいので以後スクリプトファイルのみを書くことにします。スクリプトファイル
には一行に一つづつ命令を書きます。

  え？昨日は sed -e 5q と書いたのに、今日は 2 行よけいな物がついているっ
て？ もうちょっとしたら説明しますので今の所はこのスクリプトファイルでも
標準入力の最初の 5 行が標準出力にでてくる事を確認して、ああ同じ動作をす
るスクリプトだな、と納得しておいてください。

  まず HEAD.SED で使われている命令から説明しましょう。

<descrip>
<tag>p</tag>
パターンスペースの内容と改行コードを続けて標準出力に吐き出します。
print の p と覚えておきましょう。
<tag>q</tag>
パターンスペースの内容と改行コードを続けて標準出力に吐き出した後、
実行終了。昨日も説明しましたね。quit の q です。
<tag>d</tag>
パターンスペースの内容を削除し、
標準入力から次の行をパターンスペースに読み込む。
そしてスクリプトの先頭へジャンプ。delete の d でしょう。
</descrip>
  これだけでは何の事やら良くわからないと思うので、SED の実行を順を追って
説明しましょう。標準入力から次のファイルを読み込む事にします。

<it>INPUT.TXT</it>
<code>
これは一行目だ。
こっちは二行目。
そして三行目。
つづいて四行目。
これが五行目。
六行目。
最後の七行目。
</code>

  実行は  sed -f HEAD.SED &lt; INPUT.TXT  でしたね。
  実は    sed -f HEAD.SED INPUT.TXT    とする事もできるのです。入力ファ
イルが複数ある時は  sed -f HEAD.SED INPUT1.TXT INPUT2.TXT  などとします。
この場合は INPUT1.TXT と INPUT2.TXT の二つのテキストをつなげたものが入力
となります。

  SED は起動されるとまず、標準入力の最初の行をパターンスペースにコピーし
ます。この時点でパターンスペースの内容は「これは一行目だ。」になります。
行末の改行コードは捨てられてパターンスペースの中にはありませんので注意し
てください。そして SED はおもむろにスクリプトの一行目を見ます。「5q」で
す。5 は標準入力から読み込んだテキストの「5 行目ならば」という条件でした
ね。現在読み込んでいるのは 1 行目なので残念ながら条件は成立しませんので
続く命令「q」は実行しません。

  次に SED はスクリプトの二行目を見ます。「p」。おっとこの行には条件があ
りません。この様な行は無条件に実行します。したがってパターンスペースの内
容と改行コードを続けて標準出力に吐き出します。標準出力は次のようになるで
しょう。

<code>
これは一行目だ。<改行>
</code>

  何の事はない、結果としては標準入力の 1 行目がそのまま出てきただけです。

  そして SED はスクリプトの三行目を見ます。「d」。この行も条件がありませ
んから無条件実行です。えーと、まずパターンスペースの内容を削除し、標準入
力の次の行を読み込む。この時点でパターンスペースの内容は「こっちは二行目。」
になりました。そしてスクリプトの先頭へジャンプ。

  次に SED が見るのは大方の予想通りスクリプトの一行目です。つまり SED は
「ジャンプせよ」という命令を実行しない限り、スクリプトを一行目から順に条
件が成立する行だけ実行し、「ジャンプせよ」という命令 (この場合は d です
ね) を実行すると次はジャンプの行き先の行 (この場合は一行目) から同様に見
て行くわけです。

  では「ジャンプせよ」という命令を実行することなくスクリプトの最後の行を
過ぎてしまったらどうなるのでしょうか。むなしいですね。ボーナスがいつ出る
か、いつ出るかと期待していたら年が明けてしまったようなものです。(えっ、
ボーナスって決まった日に出るのですかそりゃまたどうも失礼しました。) この
様な場合、SED は p と d を自動的に実行します。ということはつまり、スクリ
プトの最後の二行が p と d ならばその二行を削除しても同じ動作をするという
事です。ですから HEAD.SED は次のようにしても同じ動作をします。

<code>
5q
</code>

さてスクリプトの一行目は「5q」。条件が
成立しませんので何もしません。

  次にスクリプトの二行目を見て「p」。標準出力は前回の出力と合わせて次の
ようになります。

<code>
これは一行目だ。<改行>
こっちは二行目。<改行>
</code>

  もう簡単ですね。標準入力の 5 行目を読むまでは同じ事の繰り返しですから
一気に飛ばしてスクリプトの三行目「d」で標準入力の 5 行目を読むところまで
いっちゃいます。標準出力には既に

<code>
これは一行目だ。<改行>
こっちは二行目。<改行>
そして三行目。<改行>
つづいて四行目。<改行>
</code>

が出力されました。現在のパターンスペースは「これが五行目。」です。スクリ
プトの先頭にジャンプしまして、スクリプトの一行目を見ますと「5q」。現在読
み込んでいるのはずばり 5 行目ですから条件成立。「q」を実行します。

  「q」はまずパターンスペースの内容と改行コードを続けて標準出力に吐き出
します。そして実行終了。結局標準出力に吐き出された内容は次にようになりま
す。

<code>
これは一行目だ。<改行>
こっちは二行目。<改行>
そして三行目。<改行>
つづいて四行目。<改行>
これが五行目。<改行>
</code>

  お疲れさまでした。確かに標準入力の最初の 5 行が標準出力から出てきまし
たね。

  実は SED のスクリプトの基本はこれだけです。どうです、思ってたより簡単
でしょう。後は色々な「実行条件」と「命令」を一つづつ覚えて行けば良いので
す。

  命令「p」は、その時点でのパターンスペースを出力してくれるのですから、
SED の動きを詳しく調べたいときの絶好の道具になります。不可解な動作をする
スクリプトの適当な場所に p を挿入してパターンスペースの内容を確認して行
くわけです。ものは試し、HEAD.SED の適当な行に p を挿入して動作を確認して
みてください。

  最後に新しい実行条件を二つほど紹介して終わりにしましょう。

<descrip>
<tag>&dollar;</tag>
標準入力から現在読み込んでいる行が「最終行ならば」という条件。
<tag>「実行条件１」,「実行条件２」</tag>
実行条件１が成立した行から実行条件２が成立する行までの間の行。
たとえば 2,5 は「2 行目から 5 行目までの間ならば」という条件。
</descrip>
ではみなさん良い週末を。
<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第四回 「正規表現」<label id="reguler">
<p>

  SED のスクリプトは「実行条件」と「命令」からなるということは既に説明し
ました。今回は「実行条件」の全部を一気に紹介してしまいます。といっても後
二種類しかないのです。^^;)

  まず、既にやったのは
<descrip>
<tag>数字</tag>
読み込んだ行が、「数字」で示された行か？
<tag>&dollar;</tag>
読み込んだ行が、最終行か？
<tag>条件1,条件2</tag>
「条件1」が成立する行から「条件2」が成立する行までの間か？
</descrip>

  でしたね。あとは次の二つです。

<descrip>
<tag>条件!</tag>
「条件」が成り立たない行か？
<tag>/正規表現/</tag>
「正規表現」にマッチする行か？
</descrip>

  条件! は何となくわかるのではないでしょうか。たとえば 5! だと 「5 行目
以外」の行で条件成立となります。

  問題はその次ですね。一体「正規表現」とは何ものでしょう。「正規表現」は
 &dquot;regular expression&dquot; の訳なんですが、
MS-DOS のワイルドカード (ファイル
を * や ? を使って指定する方法ですね。 DEL *.* なんてコマンドラインで入
力するとファイルを全部消してくれます。) みたいなものと考えれば、まあ当た
らずとも遠からずというところでしょう。

  たとえば「S?D.*」というワイルドカードは「SED.EXE」や「SED.DOC」などと
マッチしますが、正規表現も同じような感じで特定の行とマッチするのです。と
いうことはワイルドカードの「*」や「?」に相当するものを覚えれば良いわけで
す。なあんだ、簡単じゃないか、と思うのは早計です。正規表現はワイルドカー
ドに比べてその能力が極めて高いのです。能力が高いというのはどういうことか
というと、たとえば私のダイナブックのあるディレクトリを覗くと下のようにな
ります。

 装置 S: のディスクのボリュームラベルは HARD-RAM   
 ディレクトリは S:/LESS

<verb>
.               ..              RCS             CDOS     A86    DOSLIB   A86
LESS     C      LESS     H      CDOS     OBJ    DOSLIB   OBJ    LIB      H  
MAKEFILE        LESS     OBJ    LESS     SYM    README          SHOWLINE C  
SHOWLINE OBJ    LESS     MAP    
       17 個のファイルがあります.
  1259520 バイトが使用可能です.
</verb>

  この中から、拡張子が .C と .H 以外のものを全部消したいという場合を考え
てみましょう。 .C と .H 以外のものですと、.OBJ .MAP .SYM .A86 それに 
README と MAKEFILE ですから、

<code>
A> DEL *.OBJ *.MAP *.SYM *.A86 README MAKEFILE
</code>

  とすれば良いのですが、このようなことは .C と .H 以外のファイルにはなに
があるかを知っているから出来ることであって、ひょっとしたら他に LESS.EXE 
というファイルがあるかも知れませんし、LESS.DOC などが作られることがある
かも知れません。すると、「.C と .H 以外のものを全部消す」という要求は満
たすことが出来なくなるわけです。どんなファイルがあるのかなんて見りゃわか
るだろう、などと言っちゃ駄目ですよ。人間の頭という「超」コンピュータを使
ってしまえば、もっともっと高度なことが幾らでも出来るのですから。あくまで
も、コンピュータに「自動的に」やってもらうのが目的なのです。そして、ワイ
ルドカードは「.C と .H 以外のもの全部」という表現が出来ないので、能力が
低い事になります。

  正規表現はもともと有限オートマトンで認識可能な言語集合に対応するもので
あるということになっていたのですが、コンピュータの世界では何故かはるかに
拡張された表現に対して「正規表現」という言葉が使われるようになってしまっ
ています。個人的には他の名前にした方が紛らわしくなくて良いと思っています。
あ、すみません。つい私の専攻の分野なのでよけいなことを話してしまいました。
要するに「正規表現」は強力なんだなと覚えておいてください。


  ではまず簡単なところから紹介して行きましょう。正規表現「abc」は文字列
「abc」を表します。任意の 1 文字を表すには「.」(ピリオド) を使います。
たとえば「a.c」は、文字列「abc」,「axc」, 「a@c」などを表します。
ワイルドカードの「?」と同じですね。
ピリオドを含む文字列を表したいときは、「Mt&bsol;. Fuji」
などと指定します。このように正規表現で特別の意味を持っている記号を文字と
して使いたいときは、前に「&bsol;」をつけます。
「&bsol;」自体を文字として使うときは
「&bsol;&bsol;」とすれば良いですね。

  さて、たとえば文字列「atc」は正規表現「a.c」が表現する文字列のうちの一
つですが、この時正規表現「a.c」は文字列「atc」にマッチすると言います。そ
して SED が標準入力から読み込んだ行に、正規表現がマッチするような文字列
が含まれるとき、この正規表現はその行にマッチします。つまり条件が成立する
わけです。たとえば読み込んだ行が「XYZabcxyz」だとすると、条件「/a.c/」は
成立します。

  ワイルドカードの「*」(任意の文字列を表す) に対応する正規表現の記号はな
にかといいますと、「.*」になります。なんで二文字なのかですが、「*」は直
前の正規表現の 0 回以上の繰り返しという意味なのです。つまり、「.*」は
「..」とか「....」とか 0 個以上の「.」が表す文字列のすべてを表すわけです。
任意文字が任意個つながったものですから任意の文字列ですね。

  では「a*b」はどんな文字列を表すでしょうか。たとえば「aaaab」とか
「aaaaaaaaaaaaaaaaab」とかを表します。簡単ですね。それでは「b」にはマッ
チするでしょうか。「*」は直前の文字の 0 個以上の繰り返しですから
 「a」が 0 回繰り返されているということでマッチするのです。

  どんどん進めましょう。次は「&lsqb;複数の文字&rsqb;」です。
たとえば「&lsqb;abc&rsqb;」など
と書きますとこの正規表現は「a」または「b」または「c」のいずれにもマッチ
します。つまり &lsqb;  &rsqb; の中の複数の文字の
どれでも良いというときに使います。

  みなさんは Nifty のログを整理するとき、会議室の発言のタイトルだけ抜き
取りたいと思った事はありませんか？ タイトルは例えば次のようになっていま
す。

<verb>
「003/009   GCD03723  最大公約数        SED 教室その 1」
</verb>

  最初に数字が 3 つあって次が / そしてまた数字が 3 つですね。ここの会議
室の上限は 512 だそうですからタイトル行にマッチする正規表現は次のように
なります。「/」は SED で正規表現を囲む事に使ってますので、前に「&bsol;」をつ
けて区別します。

<verb>
  [012345][0123456789][0123456789]\/[012345][0123456789][0123456789]
</verb>

  うーむ長い。いちいちこんな長いのを書くのは面倒ですので、次のように省略
して書く事ができます。

<verb>
  [0-5][0-9][0-9]\/[0-5][0-9][0-9]
</verb>

  同様にして、小文字のアルファベット一文字を表す正規表現は「&lsqb;a-z&rsqb;」にな
ります。

  さあ、では実際に正規表現を使ってスクリプトを書く事にしましょう。

<it>FIND.SED</it>
<code>
/[0-5][0-9][0-9]\/[0-5][0-9][0-9]/p
d
</code>

  SED がタイトル行を読み込んだときはスクリプトの一行目の条件が成立します
から、命令「p」が実行されて、その行を標準出力に吐き出します。タイトル行
でない時は 一行目の条件は成り立ちませんから何もしません。次に二行目は命
令「d」ですからパターンスペースの内容を捨てて次の行を読み込み、スクリプ
トの一行目へジャンプするのでしたね。

  ではやってみましょう。

<code>
W:/>sed -f find.sed < mes(17).0xx  ← 会議室の発言のファイルです。
001/009   NBF01152  ☆れお☆          SED講座はここでやりましょ！
002/009   NBF01152  ☆れお☆          失礼！間違えました！
003/009   GCD03723  最大公約数        SED 教室その 1
          << 中略 >>
「003/009   GCD03723  最大公約数        SED 教室その 1」
</code>

  あれ？最後のはなんだ？ そういえばさっきタイトルを引用しましたね。この
ように正規表現はマッチする文字列を含む行全部とマッチしますから、括弧でく
くって引用していてもマッチしてしまうのです。ではどうしたら良いのでしょう
か。「&lsqb;0-5&rsqb;&lsqb;0-9&rsqb;&lsqb;0-9&rsqb;&bsol;/…」
と行頭から書かれている行にマッチさせる事にしましょうか。
そのためには <it>FIND.SED</it> の一行目を次のように変えます。

<verb>
/^[0-5][0-9][0-9]\/[0-5][0-9][0-9]/p
</verb>



  最初に「^」がありますね。この記号はここがパターンスペースの先頭である
という事を示します。つまり SED が標準入力から 1 行分パターンスペースに読
み込んだとき、パターンスペースの最初から
「&lsqb;0-5&rsqb;&lsqb;0-9&rsqb;&lsqb;0-9&rsqb;&bsol/…」となって
いる時のみ条件が成立するのです。「^」の逆も紹介しておきましょう。「&dollar;」は
パターンスペースの最後を示します。従って行末になにか特定の文字列がある行
を探したいときに使えます。

  さて、&lsqb;0-9&rsqb; が任意の数字を表す事ができるとなると、
今度は数字でない文字
を表す方法があったら良いと思いませんでしょうか。
当然ありまして、「&lsqb;^0-9&rsqb;」
とします。abc 以外の文字でしたら「&lsqb;^abc&rsqb;」と書きます。
なんともややこしい話ですが、
&lsqb; &rsqb; の中に現れるときは「^」の意味が変わってしまうのです。でも
パターンスペースの先頭である事を意味するときは必ず正規表現の最初に現れる
から混乱する事はないでしょう。

  前述した <it>FIND.SED</it> は一行目に書かれた正規表現とマッチする行だけ標
準出力に吐き出しますので、いろいろな正規表現を一行目に書いてどんな行とマ
ッチするか実験してみてください。いろいろな表現を試してみると正規表現がど
のようなものかが良く分かるのではないかと思います。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第五回 「文字列の置換」<label id="replace">
<p>
  前回は正規表現について説明しました。実は SED では実行条件以外にも正規
表現を使うところがもう一つあります。SED の命令には文字列の置換を行う命令
がありまして、どの文字列を置換の対象にするかを指定するために正規表現が使
われるのです。まず置換命令の書き方から説明しましょう。

<descrip>
<tag>s/正規表現/文字列/</tag>
パターンスペースの中で、「正規表現」にマッチする
文字列を「文字列」と置き換えます。例えばパターンス
ペースが「abcdefg」の時「s/def/XYZ/」を実行すると、
「abcXYZg」に変わります。
</descrip>

  では実際に使ってみましょう。

<it>TITLE.SED</it>
<code>
/^[0-5][0-9][0-9]\/[0-5][0-9][0-9]/{
        s/\/[0-9 ]*/:/
        p
}
d
</code>

  スクリプトの一行目は、前回の講義でやったのと同じですね。会議室の発言の
タイトルの行で条件が成立します。ん？ 最後にある「{」は何でしょう。条件の
次は命令では？ というわけで新しい命令の紹介です。

<descrip>
<tag>{</tag>
次に「}」が現れる行までの命令を実行する。つまり、「実行条件」が成
立したとき複数の命令を実行する事が出来るのです。当然、「実行条件」
が成立しないときは「{」と「}」で囲まれた命令は実行されません。
</descrip>

  この場合でしたら、タイトル行で命令「s」(置換)と「p」(出力)を
実行するわけです。
  では二行目はどのような動きをするでしょうか。
正規表現「&bsol;/&lsqb;0-9 &rsqb;*」とマ
ッチする文字列が「:」に置換されるのですね。ふむふむ、そして三行目でパタ
ーンスペースが標準出力に吐き出されるのか。五行目は現在のパターンスペース
の内容を削除し、標準入力から次の行を読み込み、スクリプトの一行目へジャン
プします。

  さて、ではここで問題です。
命令「s/&bsol;/&lsqb;0-9 &rsqb;*/:/」によってどんな文字列が
「:」で置き換えられるのでしょう。まず「/」で始まって、数字か空白が何個か
つながったものですか。例えば、パターンスペースが

<verb>
「003/009   GCD03723  最大公約数        SED 教室その 1」
</verb>

  だと、「/009　 」(空白が 3 個) ですね。でもよく考えると「/009　」(空白
が 2 個) だってマッチしそうです。え？「/」はどうなんだって？ 「&lsqb;0-9 &rsqb;*」
は数字か空白の＊0 個＊以上の繰り返しですから、これも実は正しいのです。し
かし SED の動作が何通りもあっては混乱を招くだけですから、置換される文字
列がただ一通りに決まるように規則が決めてあります。スポーツで延々と試合が
同点のまま続いたときは最後はじゃんけんで決める、という規則が決められてい
るのと同じです (違うって？ :-)。その規則とは

<quote>
    マッチする文字列の中で、文字列の左端が一番左にあるもの。
    もし左端が一致する文字列が複数個ある時は、その中で一番長いもの。
</quote>

  を置換する文字列に選ぶのです。この場合でしたら、どの候補も左端は「/」
で同じ位置ですから、長さで勝負が決まります。
  一番長いのは「/009　 」(空白が 3 個) ですね。結局置換が行われるとパタ
ーンスペースの内容は

<verb>
「003:GCD03723  最大公約数        SED 教室その 1」
</verb>

  になります。これで正規表現にマッチする文字列がたくさんあっても、どの文
字列が選ばれるかすぐわかりますね。では次の例はどうでしょうか。

<verb>
  パターンスペース : 「bbbaaccaaa」
  命令             : 「s/a*/A/」
</verb>

  「a*」にマッチする文字列はどれでしょう。4 文字目の「a」, 4 〜 5 文字目
の「aa」, 8 文字目の「a」, 8 〜 9 文字目の「aa」, 8 〜 10 文字目の「aaa」
ですか。するとその中で左端が一番左にあるのは 4 文字目から a が始まってい
る文字列です。その中で長さが一番長いのは 4 〜 5 文字目の「aa」。だから置
換後はパターンスペースは「bbbAccaaa」になっているはずです。では実験して
みましょう。この命令は短いのでコマンドラインに書いてみます。

  まず、コマンドラインから

<code>
A>sed -e s/a*/A/  と入力して改行。標準入力はキーボードになります。次に
bbbaaccaaa        と入力して改行。
^Z                標準入力の終わりを示すために control-Z。
</code>

  SED はスクリプトの一行目「s/a*/A/」を読んで命令を実行した後二行目を読
もうとすると、もうスクリプトはありません。困ったなと思うのではなくて命令
「p」と「d」を実行するのでした (第三回 SED 教室でやりましたね)。まず、
「p」パターンスペースを標準出力に吐き出す。次に「d」パターンスペースを削
除して標準入力から次の行を読み込む。と思ったらもう標準入力はおしまいです。
標準入力がなくなってしまうと SED は何もすることがなくなってしまうので実
行を終了します。というわけで置換後のパターンスペースが画面 (標準出力) に
現れるわけです。では画面を見てみましょう。

<code>
Abbbaaccaaa
A>
</code>

  おやおや？ なにか予想したものと違うものが出てきてしまいました。4 文字
目からの「aa」は置換されず、一番前に「A」が挿入されています。一体どうし
たというのでしょうか。実はもとの文字列の「bbbaaccaaa」の先頭の長さ 0 の
文字列 (ヌルストリングと言います) が正規表現「a*」とマッチしてしまったの
です。このように長さ 0 の文字列とマッチする正規表現には注意する必要があ
ります。

  さてみなさんはエディタで、ファイルの中の特定の文字列を全部ほかの文字列
で置き換えた事はありませんでしょうか。例えば句読点を「、」「。」で全部書
いた後で「, 」「. 」に全部直すという事は時々必要になります。エディタを起
動して書き換えても良いのですが、単なる置換ですから上で説明した命令を使え
ば SED でも出来そうです。でもちょっと待って下さい。例えばパターンスペー
スが「本日は晴天なり。今日は良い天気です。」だったとします。このとき
「s/。/. /」を実行すると、「晴天なり。」の句点は置換されるのですが、二番
目の「天気です。」の句点は変換されずに残ってしまいます。もう一度置換を実
行すれば良いって？ でも句点が一行に 3 個以上あるとやっぱり困ってしまいま
す。そこで命令「s」のバリエーションとして次のものがあります。

<descrip>
<tag>s/正規表現/文字列/g</tag>
パターンスペースの中で、「正規表現」にマッチする
文字列を全て「文字列」と置き換えます。
</descrip>

  これを使えばうまく行きそうです。

<code>
A>sed -e "s/、/, /g" -e "s/。/. /g" < input.txt
</code>

  などとすれば良いでしょう。パターンスペースの読点を変換し、次に句点を変
換するわけです。(その次はもちろん「p」と「d」の実行です。)

  以上で SED の最低限 :-) の使い方は全て説明した事になります。色々なスク
リプトを書いて実験してみて下さい。

  今までは一行づつ読み込み、置換し、出力していました。次回からはちょっと
高度な使い方として、複数行をまとめて編集する方法を紹介する予定です。複数
行をまとめて扱う事によって、例えば複数行にわたるヘッダ (Nifty の会議室の
発言のヘッダも実は 2 行ですね) が編集できるようになります。
<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第六回 「セグメント」<label id="segment">
<p>
  セグメントって何？ 左官屋さんが塗っているものですね。(それはセメント)

  前回までの講義で、標準入力から読み込んだ 1 行を SED でいかに料理できる
かおわかり頂けたと思います。でも 1 行だけではなく複数行をまとめて加工し
たいときがあります。例えば次のような Nifty の利用時間リストを作りたいと
します。

<verb>
 LOG IN               LOG OUT              利用時間
 91/12/06 02:12:34 〜 91/12/06 02:17:10 ： 04分36秒
 91/12/07 14:12:59 〜 91/12/07 14:17:34 ： 04分35秒
 91/12/07 15:23:55 〜 91/12/07 15:30:10 ： 06分15秒
 91/12/07 18:58:22 〜 91/12/07 19:03:03 ： 04分41秒
 91/12/08 23:46:45 〜 91/12/08 23:53:42 ： 06分57秒
 91/12/09 18:51:36 〜 91/12/09 18:55:21 ： 03分45秒
</verb>

  この様な表があると、12/7 は何回もアクセスしている、何故かな？ あ、土曜
日か。とか、一回あたりの利用時間は似たようなものでだいたい 5 分程度か、
などが分かって便利です。

  この表は Nifty が LOG OUT 時に表示する

<verb>
>        LOG IN  −−− 91/12/06    02:12:34
>        LOG OUT −−− 91/12/06    02:17:10
>
>　ご利用時間は、　04分36秒でした。
</verb>

  を加工すれば作ることが出来そうですが、Nifty は LOG IN/OUT 時間と利用時
間を同じ行に表示してくれませんので、この 4 行を一行にまとめる必要があり
ます。まとめてしまえば後は置換命令「s」で不要な文字を置き換えてしまえば
良いので簡単ですね。というわけで複数行を扱うための命令の紹介です。

<descrip>
<tag>N</tag>
     標準入力から一行読み、パターンスペースに追加します。この命令を実
     行することにより、標準入力の複数行の内容をパターンスペースに記憶す
     ることが出来るのです。この時パターンスペース内の複数行は改行コード
     によって区切られています。そして改行コードによって区切られたそれぞ
     れの文字列を「セグメント」と呼びます。
<tag>P</tag>
     パターンスペースの最初のセグメントと改行を標準出力に吐き出します。
     要するに命令「p」のセグメント版です。
<tag>D</tag>
     パターンスペースの最初のセグメントを削除し、スクリプトの先頭へジ
     ャンプします。ただし削除後パターンスペースが空になった場合は標準入
     力から次の行をパターンスペースに読み込みます。
       つまりこの命令は、パターンスペースにセグメントが一つしかないとき
     (つまり、パターンスペースに改行コードが含まれていない時) は、命令
     「d」と同じ動作をします。
</descrip>

  たとえばパターンスペースの内容が「現在の行」という文字列の時、命令「N」
を実行して標準入力から「次の行」という行を読み込んだ場合、パターンスペー
スの内容は「現在の行&lt;改行&gt;次の行」になります。この時次に命令「p」を実行
すると標準出力に

<code>
現在の行<改行>
次の行<改行>
</code>

  と吐き出されます。ところが「P」を実行すると出力されるのは

<code>
現在の行<改行>
</code>

  だけです。また、命令「D」を実行すると最初のセグメントが削除され、パタ
ーンスペースの内容は「次の行」になります。セグメントを区切る改行コードも
一緒に削除されることに注意してください。削除後スクリプトの先頭にジャンプ
します。パターンスペースにはまだセグメントが一つ残っているので標準入力か
らの読み込みは行われません。

  では早速この命令を使ってみます。Nifty の利用時間のリストを出力するスク
リプトです。

<it>TIME.SED</it>
<code>
1i\
 LOG IN               LOG OUT              利用時間
/^[ ]*LOG IN[ ]*−/{
        N
        N
        N
        s/\n/ /g
        s/LOG OUT/〜/
        s/ご利用時間は、/：/
        s/でした。//
        s/[A-Z−]*//g
        s/[ 　][　 ]*/ /g
        p
}
d
</code>

  SED は起動されるとまず標準入力から 1 行読み込んでパターンスペースに記
憶し、スクリプトの一行目を見るのでしたね。実行条件は「1」、現在読み込ん
でいるのは標準入力の 1 行目ですから条件成立。命令「i」を実行します。

  命令「i」は、今まで紹介した命令と異なり、二行にわたっています。一行目
の行末に「&bsol;」がある事に注意してください。

<bf>i&bsol;<newline>
文字列
</bf>
<quote>
「文字列」を標準出力に吐き出します。空白, タブを行頭に出
力する時と、改行を出力する時は、それらの文字の前に「&bsol;」
をつけます。
</quote>

  たとえば

<code>
i\
\
\ この行の最初には空白がある。\
\	この行の最初にはタブがある
</code>

  という命令を実行した場合を考えます。この命令は四行にわたっていますが、
二行目と三行目の行末には「&bsol;」がありますね。つまり二行目と三行目の終わり
の改行は、命令の終わりを意味する改行ではなく、命令「i」によって出力され
る文字列の中に含まれる改行文字なのです。したがって実行しますと、標準出力
に次のような文字列が吐き出されます。

<code>
<改行>
 この行の最初には空白がある。<改行>
	この行の最初にはタブがある<改行>
</code>

  同様にして命令「s」の置換文字列に改行コードを入れる事もできます。
  たとえば

<code>
s/改行/\
/
</code>

  はパターンスペース内の「改行」という文字を改行コードに置き換えます。

  さて、スクリプトの一〜二行目は

<code>
1i\
\ LOG IN               LOG OUT              利用時間
</code>

  ですから、最初に空白文字が出力され次に「LOG IN …」が出力されるわけで
す。

  さてスクリプトの一〜二行目が済みましたから次は三行目です。
  三行目は「/^&bsol; &lsqb;*LOG IN&lsqb; &rsqb;*−/{」ですから、
パターンスペースの内容が正規
表現「^&lsqb; &rsqb;*LOG IN&lsqb; &rsqb;*−」とマッチするときのみ
「{」と「}」で囲まれた命令
が実行されるのでした。マッチしない場合は「}」の次の行まで一気に飛んで、
命令「d」を実行します。つまり、パターンスペースの内容を削除し、標準入力
から次の行をパターンスペースに読み込んで、スクリプトの先頭へジャンプしま
す。つまり、スクリプトの三行目の正規表現にマッチするまで SED はひたすら
標準入力を 1 行づつ読み続けるのです。ご苦労様。

  ではスクリプト三行目の正規表現は、どんな場合にマッチするのでしょうか。
えーと、パターンスペースの先頭 (「^」) から
0 個以上の空白 (「&lsqb; &rsqb;*」) が
あってその次に文字列「LOG IN」、また 0 個以上の空白があって次に文字「−」、
残りは何でも良い。従って Nifty の LOG OUT 時に表示される

<verb>
>        LOG IN  −−− 91/12/06    02:12:34
</verb>


  の行を、SED が読み込んだ時に成立します。

  さてマッチする行が見つかるとスクリプトの四行目以降を実行することになり
ます。まず紹介したばかりの命令「N」です。三回続けて「N」を実行します。す
るとパターンスペースは

<verb>
「        LOG IN  −−− 91/12/06    02:12:34<改行>
        LOG OUT −−− 91/12/06    02:17:10<改行>
<改行>
　ご利用時間は、　04分36秒でした。」
</verb>

になります(都合上、改行していますが&lt;改行&gt;と
書いてあるところ以外は改行はありません)。
長いので見にくいのですが、要するにこれで 
Nifty が LOG OUT 時に表示する LOG IN/OUT の時間と利用時間の表示を行って
いる 4 行がすべてまな板 (パターンスペース) の上にのったわけです。あとは
置換命令「s」で料理するだけです。

  最初の置換命令は「s/&bsol;n/ /g」です。見慣れない正規表現が出てきました。

<descrip>
<tag>&bsol;n</tag>
パターンスペース内の改行文字とマッチする正規表現。
</descrip>

  したがって、パターンスペース内の改行文字 (「&bsol;n」) を全て (「g」) 空白
で置換するわけです。すでに説明したように、改行コードはパターンスペースで
セグメントを区切るために使われていました。それ故この置換によって 4 つあ
ったセグメントは 1 つのセグメントに合併してしまうのです。つまり、パター
ンスペースの内容は

<verb>
「        LOG IN  −−− 91/12/06    02:12:34         
LOG OUT −−− 91/12/06    02:17:10  　ご利用時間は、　04分36秒でした。」
</verb>

になります(都合上、改行していますが実際は改行はありません)。

  残りの置換命令はすでに説明済みのものばかりですから説明は不要でしょう。
最後の置換命令「s/&lsqb; 　&rsqb;&lsqb;　 &rsqb;*/ /g」の
「&lsqb;」と「&rsqb;」で囲まれている文字は、
全角と半角の空白文字です。したがってこの命令は 1 個以上の全角または半角
の空白文字を 1 個の半角の空白文字で置き換えます。

  そして置換が全部済んだ後、命令「p」で出来た料理 (パターンスペース) を
標準出力に出して一丁あがり、最後の命令「d」で後かたづけをして次の行を標
準入力から読み、スクリプトの先頭へジャンプ、ということになります。

  いかがでしたでしょうか。ちょっと複雑なスクリプトでも一つづつ命令を追っ
て行けば意外と簡単ですね。

  いままでに説明した命令だけでかなり複雑な事を SED にやらせる事が出来る
と思いますので、みなさんもなにかスクリプトを書いてみてはいかがでしょう。
次回は SED の最強の機能を紹介したいと思います。乞うご期待。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第七回 「タグ付き正規表現」
<p>
  前回、複数行を加工する方法について説明しましたが、肝心の加工自体は不要
な文字を命令「s」で消したり他の文字に置き換えると言った比較的単純なもの
に限られていました。
  したがって会議室の発言のログから次のようなタイトル一覧 (RT コマンドで
表示されます) を作ろうとすると困ってしまいます。

<verb>
＜タイトル一覧＞
番号 ID       登録日      TO   CO   題名
   1 NBF01152 12/07 07:49       1   SED講座はここでやりましょ！
   2 NBF01152 12/07 08:01 001       失礼！間違えました！
   3 GCD03723 12/07 08:02       1   SED 教室その 1
</verb>

  なぜなら、発言のタイトルは次のような形式になっているからです。

<verb>
>003/009   GCD03723  最大公約数        SED 教室その 1
>(17)   91/12/07 08:02                   コメント数：1
</verb>

  タイトルが 2 行にわたっているのは前回の複数行を扱う命令「N」でなんとか
なるとして、問題は並んでいる文字の順番です。つまりタイトルでは、番号、ID、
題名、登録した日付、コメントの有無の順なのですが、タイトル一覧では題名が
最後になっています。
  今までの方法ですと、タイトル一覧では ID の次に登録日付が来ますから、ID
 の後から日付の前までを消去する事になりますが、そうすると題名も一緒に消
えてしまいます。なんとか消す前に題名を覚えておけばうまく行きそうです。と
いうわけで新しい正規表現の紹介です。

<descrip>
<tag>&bsol;(正規表現&bsol;)</tag>
「正規表現」にマッチした文字列を記憶します。
</descrip>

  物事を忘れるという事は脳ミソから記憶した事柄がなくなるのではなくて、事
柄自体は残っているのだけれども、その呼出が出来なくなるだけだという説があ
りますが、それと同じでいくら記憶したと言い張っても、呼び出す方法がなけれ
ばナンセンスです。したがって呼び出すための正規表現があります。

<descrip>
<tag>&bsol;1</tag>
正規表現の一番左にある「&bsol;(」と対応する「&bsol;)」で囲まれた部分にマッ
チする文字列を呼び出します。
<tag>&bsol;2</tag>
正規表現の左から２番目にある「&bsol;(」と対応する「&bsol;)」で囲まれた部分
にマッチする文字列を呼び出します。
</descrip>

  以下同様で、 &bsol;3 &bsol;4 … &bsol;9 まであります。
このような記憶と呼出が出来る機
能を持つ正規表現を「タグ付き正規表現」と呼んで、これらの機能を持たない正
規表現と区別する事があります。「タグ付き」のものとそうでないものとは、能
力的には大違いで、後者が通常の意味での正規表現で、表現する文字列を有限オ
ートマトンで認識できるのに対し、前者はチューリングマシンでしか認識できな
い文字列集合を表現する事が出来ます。また余計な事を話してしまいました ^^;)
 が、要するにこの「タグ」が付く事によって、はるかに多様な文字列が表現出
来るようになっているという事です。

  さて、「&bsol;(正規表現&bsol;)」は「正規表現」が
マッチする文字列とマッチします。
たとえば文字列「abcdefg」は正規表現
「abc&bsol;(&lsqb;a-z&rsqb;*&bsol;)g」にマッチします。
つまり、マッチするかどうかの判定に関しては、
「&bsol;(」と「&bsol;)」は何の影響も及ぼ
しません。今までの正規表現と異なるのは、「&bsol;(」と「&bsol;)」
で囲まれた部分にマッチする文字列を記憶し、
「&bsol;1」でその記憶を呼び起こす事が出来る点にありま
す。すなわちこの場合でしたら、
「&bsol;1」は「def」と同じ意味になります。ここ
で注意しなければならないのは
「&bsol;1」が意味する文字列は「&bsol;(&lsqb;a-z&rsqb;*&bsol;)」がマッ
チする文字列に従って変化する事です。

  さらに &bsol;1 … &bsol;9 の正規表現は、
置換命令「s」の置換文字列中に用いる事が
可能なのです。たとえば
「s/^&bsol;(.&bsol;)&bsol;(.&bsol;)/&bsol;2&bsol;1/」
などと出来ます。この置換命
令の正規表現から「&bsol;(」と「&bsol;)」を取り除くと、
「^..」になりますから先頭の
任意の二文字にマッチすることがわかります。最初の「.」は一文字目、次の「.」
は二文字目にマッチするので、「&bsol;(」と「&bsol;)」を
それぞれにつけることによって、
「&bsol;1」に一文字目、「&bsol;2」に二文字目が記憶されます。
  置換文字列は「&bsol;2&bsol;1」ですので、
「&bsol;2」に記憶された文字列 (二文字目) と
「&bsol;1」に記憶された文字列 (一文字目) を続けた文字列 (二文字を逆順にしたも
の) で置換されるわけです。結局この置換によって最初の二文字が逆順になりま
す。

  「&bsol;(」と「&bsol;)」は、使い方が慣れるまではちょっと難しいでしょうから、もう
ちょっと練習してみましょう。
  パターンスペースが「abcdefgVWXYZ0123」の時、
  命令「s/&bsol;(&lsqb;a-z&rsqb;*&bsol;)
&bsol;(&lsqb;A-Z&rsqb;*&bsol;)/&bsol;2 &bsol;1/」
を実行するとパターンスペースの内容はどうなるでしょうか。
  「&bsol;(」と「&bsol;)」を取り除いて考えると、
「&lsqb;a-z&rsqb;*&lsqb;A-Z&rsqb;*」ですから英小文字 
(「&lsqb;a-z&rsqb;」) の 0 回以上の繰り返し (「*」) と
英大文字 (「&lsqb;A-Z&rsqb;」) の 0 回
以上の繰り返しがつながったものにマッチします。従って「&lsqb;a-z&rsqb;*」は
「abcdefg」、「&lsqb;A-Z&rsqb;*」は「VWXYZ」にそれぞれマッチします。
故に「&bsol;1」は
「abcdefg」、「&bsol;2」は「VWXYZ」となり、
置換文字列は「VWXYZ abcdefg」になります。
  その結果、置換後のパターンスペースは「VWXYZ abcdefg0123」になるわけです。

  この辺は習うより慣れる方が速いので、いろいろと実験してみてください。

  次のスクリプトは、会議室の発言のログファイルを読んでタイトル一覧を出力
します。タイトルを加工するために沢山の命令「s」とタグ付き正規表現を使っ
ています。複雑ですが、命令「s」の前後でパターンスペースの内容がどのよう
に変化するか、命令「p」を挿入して調べれば、理解する事はそう難しくはない
でしょう。

<it>RT.SED</it>
<code>
1i ＜タイトル一覧＞\
番号 ID       登録日      TO   CO   題名
/^[0-9][0-9][0-9]\/[0-9][0-9][0-9]   [A-Z]/!d
/発言者削除/d
s/^\(...\).... *\([^ ]*\).*  /\1 \2\
/
s/^0/ /
s/^ 0/  /
N
s/^\(.*\n.*\n\)[ ()0-9]*\/\([0-9/]* [0-9:]*\) */\1\2\
/
/^.*\n.*\n.*\n.*へのコメント/{
    s/へのコメント */ /
    b endTO
}
s/^\(.*\n.*\n.*\n\) *\(.*\)$/\1    \2/
:endTO
/コメント数：[0-9]*$/{
    s/コメント数：//
    /[^0-9][0-9]$/s/\([0-9]\)$/  \1/
    /[^0-9][0-9][0-9]$/s/\([0-9]*\)$/ \1/
    b endCO
}
s/$/   /
:endCO
s/^\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)$/ \1 \3 \4   \2/
</code>

  おっと、まだ紹介していない命令がありました。

<descrip>
<tag>b ラベル</tag>
「:ラベル」がある行へジャンプ。
<tag>:ラベル</tag>
ジャンプ先。
</descrip>

  たとえば「b abc」という命令を実行すると、「:abc」と書かれている行へジ
ャンプします。

  ちょっと難しいかも知れませんね ^_^;)。ヒントを書きましょう。十〜十一行
目の命令「s」(十行目の行末に「&bsol;」がある事に注意して下さい、置換文字列中
に改行コードを入れたいときはこうするのでした) を実行する前のパターンスペ
ースの内容が例えば次のようだったとします。

<code>
  3 GCD03723
SED 教室その 1
(17)   91/12/07 08:02                   コメント数：1
</code>

<verb>
s/^\(.*\n.*\n\)[ ()0-9]*\/\([0-9/]* [0-9:]*\) */\1\2\
/
</verb>

  を実行すると、「  3 GCD03723&lt;改行&gt;SED 教室その 1&lt;改行&gt;」が
「^&bsol;(.*&bsol;n.*&bsol;n&bsol;)」にマッチします。
そして「(17)   91/」が「&lsqb; ()0-9&rsqb;*&bsol;/」、
「&bsol;(&lsqb;0-9/&rsqb;* &lsqb;0-9:&rsqb;*&bsol;)」が
「12/07 08:02」にマッチして、
「 *」は「コメント」
の直前までの沢山の空白にマッチするわけです。従って、命令実行後のパターン
スペースは

<code>
  3 GCD03723
SED 教室その 1
12/07 08:02
コメント数：1
</code>

  となります。

  もうちょっと詳しく説明したかったのですが、残念ながら紙数が尽きてしま 
(うわけないか ^^;) いました。もしわかりにくいところがありましたら、質問
してください。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)


<sect>SED 教室 第八回 「有象無象」
<p>
  SED 教室もおかげさまで八回目を迎えました。前回までの講義で SED の最も
よく使われる (と私が勝手に思っている) 命令の説明が一応終わりましたので、
今回は趣向を変えて色々なスクリプトを紹介して、みなさんがスクリプトを書く
ときの参考にしていただく事にしましょう。

  今回からスクリプトに行番号を付ける事にしました。SED で実行するときは行
頭の行番号を ( SED で :-) 削除してから使ってください。

  いままで紹介してきたスクリプトは比較的(?) 実用度が高いものでした。実用
一点張りでは面白くないということでちょっと遊んでみます。

<it>REV.SED</it>
<code>
  1 s/$/\
  2 /
  3 :loop
  4 /..*\n/{
  5   s/\(.\)\(.*\)\n/\2\
  6 \1/
  7   b loop
  8 }
  9 s/\n//
</code>

  左端の数字 (とそれに続く空白) は行番号ですので入力しないでくださいね。

  一〜二行目の置換命令でパターンスペース内にセグメントを二つ (二番目のセ
グメントはヌルストリング) 作り、五〜六行目の置換命令で前のセグメントの先
頭文字を二番目のセグメントに一文字づつ移していきます。前のセグメントに文
字がなくなれば、ループを抜けて最後の置換命令でセグメントの区切りを削除し
て、パターンスペースを標準出力に吐き出す、というわけです。
  たとえばパターンスペースの内容が「abc」だとすると、一〜二行目の置換に
より「abc&lt;改行&gt;」になって、
四行目の条件「/..*&bsol;n/」が成立するから、次の行
の置換で「bc&lt;改行&gt;a」になります。
「:loop」にジャンプして再び置換、今度は
「c&lt;改行&gt;ba」になります。
同様にして「&lt;改行&gt;cba」になると四行目の条件が成
立しなくなりますから、九行目の置換によって「cba」になるわけです。面白い
でしょう？ :-)

  ところで、四行目の実行条件の正規表現「..*&bsol;n」と、五行目の置換命令の正
規表現「&bsol;(.&bsol;)&bsol;(.*&bsol;)&bsol;n」が、
「&bsol;(」「&bsol;)」を除けば同じであることに気づきま
したでしょうか。同じことを二度書くのは無駄ということで、<it>REV.SED</it>
は次のように書き換えることができます。

<it>REV2.SED</it>
<code>
  1 s/$/\
  2 /
  3 :loop
  4 s/\(.\)\(.*\)\n/\2\
  5 \1/
  6 t loop
  7 s/\n//
</code>

  命令「b」の代わりに新しい命令を使っています。

<descrip>
<tag>t ラベル</tag>
直前の置換命令の実行で、実際に置換が行われたならば
「:ラベル」の行にジャンプする。
</descrip>

  命令「t」でジャンプするのは置換が行われたときですが、ではどんな時に置
換が行われるのでしょう。もちろん置換命令の正規表現がパターンスペースにマ
ッチするときですね。したがって、

<verb>
  s/正規表現/置換文字列/
  t ラベル
</verb>

  と

<verb>
  /正規表現/{
    s/正規表現/置換文字列/
    b ラベル
  }
</verb>

  は全く同じ動作をします。このように命令「t」を使ったスクリプトは常に命
令「b」を使ったスクリプトで書き換え可能なので、命令「t」は本質的に不要な
のですが、命令「t」を使った方が同じ「正規表現」を二度書かなくて済むので、
スクリプトが短くなりますし、実行も速くなります。

  次はちょっと実用的なものです。標準入力から読み込んだテキストの最後の 5
 行だけ標準出力に吐き出します。

<it>TAIL.SED</it>
<code>
  1 :loop
  2 $q
  3 N
  4 2,5b loop
  5 D
</code>

  まず三行目の「N」でパターンスペース内に標準入力の 2 行分が記憶されます。
そして標準入力の 2 行目から 5 行目までは、スクリプト四行目の条件「2,5」
が成立しますから、一行目にジャンプして三行目の「N」が繰り返し実行されま
す。結局標準入力の 1 行目から 6 行目まで記憶された後「2,5」の条件が成立
しなくなって五行目の「D」が実行されます。つまりパターンスペースの最初の
セグメントが削除され、スクリプトの先頭へジャンプします。このときパターン
スペース内には、標準入力の 2 行目から 6 行目までの計 5 行分が記憶されて
います。
  後は「N」と「D」が交互に実行されますので、標準入力から 1 行づつ読み込ん
でパターンスペースの最後尾にセグメントを追加しつつ、先頭からセグメントを
一つづつ削除していくことになります。従ってスクリプトの二行目では常にパタ
ーンスペース内に 5 行分が記憶されているわけです。そして標準入力の最終行
を読んだとき「&dollar;」の条件が成立して、
「q」を実行。つまり、パターンスペース
の内容を標準出力に吐き出して終了します。

  では次のスクリプトは何をするでしょうか。

<it>BOTTOMUP.SED</it>
<code>
  1 :loop
  2 N
  3 s/\(.*\)\n\(.*\)/\2\
  4 \1/
  5 $!b loop
</code>

  このスクリプトの面白いところは、一度も「d」あるいは「D」が実行されない
という点にあります。つまり標準入力を全部パターンスペースに記憶してしまう
のです。当然 SED が記憶できる容量には限度がありますから、標準入力の行数
が多いと実行できません。
  スクリプトの五行目に「&dollar;!b loop」とありますから、
標準入力の最後の行を読
むまでスクリプトの一行目〜五行目が繰り返し実行されます。つまり「N」と「s」
が交互に実行されるわけです。「N」は標準入力から 1 行読んで、パターンスペ
ースの＊最後尾に＊セグメントとして追加されるのでした。では次の「s」は何
をしているのでしょう？ 実は最後尾のセグメントを一番先頭に持ってくるので
す。結局新たに標準入力から読み込んだ行を、パターンスペースの＊先頭に＊セ
グメントとして追加することになります。標準入力から読み込んだ行をどんどん
パターンスペースの先頭に追加していくとどうなるか、... もうおわかりですね。

  三〜四行目の置換命令がちょっと難しいかも知れません。たとえば次のファイ
ル <it>INPUT.TXT</it> を標準入力から読んだ場合を考えます。

<it>INPUT.TXT</it>
<code>
これは一行目だ。
こっちは二行目。
そして三行目。
つづいて四行目。
これが五行目。
</code>

  スクリプトの二行目の「N」で標準入力の 3 行目を読んだ時点で、パターンス
ペースの内容は次のようになります。(命令「p」を「N」の次の行に挿入する事
により確認できるでしょう。)

<code>
こっちは二行目。<改行>
これは一行目だ。<改行>
そして三行目。
</code>

  次の置換命令「s」でパターンスペースはどう変化するでしょうか。正規表現
「&bsol;(.*&bsol;)&bsol;n&bsol;(.*&bsol;)」には、
改行コードを含むすべての文字列がマッチするから大
変です。たとえば、「二行目。&lt;改行&gt;これは一」とか、
「。&lt;改行&gt;そして三行」
とか、何でもありです。でも実際に置換される文字列は、SED 教室 第五回で説
明したように、

<quote>
マッチする文字列の中で、文字列の左端が一番左にあるもの。
もし左端が一致する文字列が複数個ある時は、その中で一番長いもの。
</quote>

  ですから、「こっちは二行目。&lt;改行&gt;これは一行目だ。
&lt;改行&gt;そして三行目。」
です。この文字列はパターンスペース全部だから、左端が一番左で一番長く、文
句無しです。
  ところが、この正規表現には「&bsol;(」と
「&bsol;)」が使ってあります。単に置換され
る文字列が分かっただけでは不十分なのです。
すなわち、「&bsol;1」と「&bsol;2」に記憶
された文字列が分からなければ、置換文字列が分かりません。「&bsol;1」は前半の
「.*」がマッチした文字列が記憶され、「&bsol;2」は後半の「.*」がマッチした文字
列が記憶されます。前半/後半の「.*」がマッチした文字列はどれでしょう？
  二つの「.*」の間に改行コードである事が必要なので、次の二つのうちのどち
らかだと思われます。

<descrip>
<tag>候補その 1</tag>
         前半の「.*」（&bsol;1）  「こっちは二行目。」
         後半の「.*」（&bsol;2）  
        「これは一行目だ。&lt;改行&gt;そして三行目。」
<tag>候補その 2</tag>
         前半の「.*」（&bsol;1）  
        「こっちは二行目。&lt;改行&gt;これは一行目だ。」
         後半の「.*」（&bsol;2）  「そして三行目。」
</descrip>

  確か、SED は一番長い文字列を置換するのだから、マッチする文字列の長い方
だろうと思うのですが、前半の「.*」は候補その 2 の方が長く、後半の「.*」
は候補その 1 の方が長いので困ってしまいます。あちらたてればこちらがたた
ず、というわけでどちらを優先するか規則を決めておかないと、前半の正規表現
「.*」と後半の正規表現「.*」の間で喧嘩になってしまいます。(ならないって)
  そこで次のような規則が決められています。

    左にある正規表現がマッチする文字列が一番長くなるものを選ぶ。

  この場合でしたら、前半の「.*」が優先されるわけです。つまり候補その 2 
です。従って置換文字列「&bsol;2&lt;改行&gt;&bsol;1」は

<code>
そして三行目。<改行>
こっちは二行目。<改行>
これは一行目だ。
</code>

  になり、パターンスペース全体がこの文字列で置き換えられます。結局、最後
尾のセグメント「そして三行目。」が一番先頭に移動しました。

  正規表現の置き換えは、このように結構複雑ですから慣れるまでは難しいと思
います。最初のうちは思わぬ部分が置換され、スクリプトが思った通りに動かな
い事が多いと思いますが、try and error で頑張ってください。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第九回 「ホールドスペース」
<p>
  前回までに紹介した命令だけで SED のスクリプトをすべて書く事が可能なの
ですが、ほかに知っておくとスクリプトが短く書けるという命令があります。た
とえば次の命令です。

<descrip>
<tag>y/文字列１/文字列２/</tag>
パターンスペース中の文字のうち、「文字列
１」に含まれる文字を、「文字列２」の同じ位
置にある文字に置き換える。「文字列１」と
「文字列２」は同じ長さでなければならない。
</descrip>

  例えば「y/abc/XYZ/」という命令を実行すると、パターンスペース中の文字
「a」は「X」で、「b」は「Y」で、「c」は「Z」でそれぞれ置き換えられるわけ
です。つまり、次の三つの置換命令の略記です。

<verb>
  s/a/X/g
  s/b/Y/g
  s/c/Z/g
</verb>

  このように、命令「y」を使うと、「s」だけで書くときに比べてスクリプトを
短く書けます。他にも便利な命令がいくつかありますが、ちょっと複雑なスクリ
プトを書くとき有効なのが、ホールドスペースです。

  ホールドスペースはパターンスペースと同様、文字列を記憶しておける場所で
す。パターンスペースには命令「N」などを使って複数行を記憶させる事が可能
ですから、本当はパターンスペース以外に記憶場所が無くても良いのです。でも、
ホールドスペースを使った方がスクリプトが簡単に書ける場合があります。たと
えば次に紹介するスクリプトなどです。次のスクリプトがホールドスペースを使
わないで書くとどうなるか、考えてみるのも面白いでしょう。

  さて、前回の SED 教室からスクリプトに行番号がつきましたが、実は次のス
クリプトを使ってつけたのです。

<it>NUMBER.SED</it>
<code>
  1 x
  2 1s/.*/   /
  3 H
  4 y/ 0123456789/11234567890/
  5 G
  6 s/.*\([^0]0*\)\n\(.*\)\n\(.*\)[^9]9*$/\3\1 \2/
  7 h
  8 s/\( *[0-9]*\).*/\1/
  9 /^9*$/s/^/ /
 10 x
</code>

  行番号をつけるためには、数字を 1 増やす変換を行わなければなりません。
では、数字が 1 つづつ増えていく時、数字はどのように変化するのでしょうか。
たとえば「13」の次は「14」、「7599」の次は「7600」ですね。まず気づくのは
変化する桁と変化しない桁があるという事です。たとえば「13」と次の「14」を
比べると十の位の数字は変化しません。もう一つの特徴は、変化する桁は必ず数
字が一つ増える ( 9 の次は 0 ) という事です。‥‥ なんて書いていてもばか
ばかしいほど当たり前の事ですね。:-)
  変化する桁に関しては次の命令でうまく行きそうです。

<verb>
  y/ 0123456789/11234567890/
</verb>

  空白を「1」に置換しているのは、「 9」の次が「10」だからです。
  ところがこの命令を実行すると、変化しない行まで置換してしまいます。従っ
て置換が及ばない場所に置換すべきでない行を保存しておく必要があります。
  この保存するための場所がホールドスペースです。

  ホールドスペースは、パターンスペースと同様文字列を保存しておける場所で
すが、パターンスペースの内容が標準入力から文字列を読み込んだり、置換を実
行する事によって変化するのに対し、ホールドスペースはいままで紹介してきた
命令によっては変化しません。ホールドスペース関連の命令は次の通りです。

<descrip>
<tag>h</tag>
パターンスペースの内容をホールドスペースに保存します。それまでの
ホールドスペースの内容は失われます。hold の h です。
<tag>H</tag>
パターンスペースの内容を、セグメントとしてホールドスペースに追加
します。すなわちホールドスペースの最後に改行コードとパターンスペー
スの内容を付加します。「h」のセグメント版です。
<tag>g</tag>
ホールドスペースの内容をパターンスペースに読み込みます。それまで
のパターンスペースの内容は失われます。get の g です。
<tag>G</tag>
ホールドスペースの内容を、セグメントとしてパターンスペースに追加
します。すなわちパターンスペースの最後に改行コードとホールドスペー
スの内容を付加します。「g」のセグメント版です。
<tag>x</tag>
パターンスペースの内容とホールドスペースの内容を交換します。
eXchange の x です。
</descrip>

  さて、パターンスペースにある数字を 1 増やす方法について考えます。たと
えばパターンスペースの内容が「12399」という数字であるとしましょう。この
場合変化しない桁は上位二桁の「12」です。この二桁だけホールドスペースに保
存しても良いのですが、めんどうなので :-) 「H」で全桁ホールドスペースに保
存します。ここで「h」の代わりに「H」を使う理由はホールドスペースの内容を
消したくないからです。この時点でのホールドスペースの内容は次のようになり
ます。

<verb>
「<ホールドスペースの元の内容><改行>12399」
</verb>

  次に「y/ 0123456789/11234567890/」でパターンスペースの各桁の数字を 1 
増やします。すると「23400」になりますが、正しいのは下 3 桁だけです。つま
り、下位桁から見て行って、最初の「0」でない桁までは正しくなります。(ちょ
っと考えれば当たり前の事ですね。) 一方保存した数字「12399」の下 3 桁は不
要です。つまり、下位桁から見て行って、最初の「9」でない桁までが不要とな
るわけです。

  さて保存した数字をパターンスペースに持ってくるために命令「G」を実行し
ます。するとパターンスペースの内容は次のようになります。

<verb>
「23400<改行><ホールドスペースの元の内容><改行>12399」
</verb>

  「400」と「12」を組み合わせれば、「12399」の次の数字「12400」を得る事
が出来ます。「400」は、最初のセグメントの下位桁から見て行って最初の 0 で
ない桁までですから、正規表現「.*&bsol;(&lsqb;^0&rsqb;0*&bsol;)&bsol;n」
を使います。つまり最初の
「.*」に不要な上位 2 桁がマッチし、必要な下位 3 桁を記憶します。
  一方、「12」は、最後のセグメントの下位桁から見て行って最初の 9 でない
桁までを除いた物ですから、
正規表現「&bsol;n&bsol;(.*&bsol;)&lsqb;^9&rsqb;9*&dollar;」を使います。つまり
最初の「.*」に必要な上位 2 桁がマッチするので、その 2 桁を記憶するのです。
  従って、

<verb>
  s/.*\([^0]0*\)\n\(.*\)\n\(.*\)[^9]9*$/\3\1 \2/
</verb>

  を実行すると「&bsol;1」に「400」が、
「&bsol;3」に「12」が記憶されますから、パタ
ーンスペースの内容は次のようになります。

<verb>
「12400 <ホールドスペースの元の内容>」
</verb>

  見事「12399」の次の数字「12400」を得る事が出来ました。こうしてみると
&lt;ホールドスペースの元の内容&gt; 
というのは標準入力から読み込んだ行であれば
数字が行番号になって丸くおさまる事が分かります。標準入力から読み込んだ行
をホールドスペースに記憶するには行頭で命令「x」を実行すれば OK です。

  さて、このように SED で行番号をつけようとすると結構複雑なスクリプトを
書く事になるわけですが、ミドリノセロー氏による日本語対応 GNU 版 SED では
行番号を扱えるエスケープシーケンスが追加されている為、<it>NUMBER.SED</it>
は次のように簡単に書けてしまいます。困ったものだ。^_^;) でも、数字を 1 
増やすテクニックは色々なところで応用がききますから、憶えて損はないでしょ
う。

<it>NUMBER2.SED</it>
<code>
  1 s/^/  \L#/
  2 s/^ *\([0-9]*[0-9 ][0-9 ][0-9 ]\)#/\1 /
</code>

  一行目の置換文字列中の「&bsol;L」は現在読み込んでいる標準入力の行番号を表し
ます。二行目で行番号が 3 桁以上の場合は行頭の空白を削除します。
<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第十回 「文字列の置換、再論」
<p>
  文字列の置換は、SED の最も基本的な命令であるとともに、最もよく使われる
テクニックであり、さらに最も強力な機能です。命令の紹介は SED 教室の第五
回で、強力な機能である「タグ付き正規表現」に付いては SED 教室第七回で説
明しました。が、テクニックに関しては一通り紹介しただけなので、もう少し詳
しく説明してみたいと思います。

  SED 教室の原稿は、一行に半角 72 文字詰めで書いていますが、これをもし一
行に半角 50 文字詰めに変換したいとしたら、みなさんはどうしますか？ フリー
ウェア、市販ソフトを含めて相当数のテキスト整形ツールが出回っていますから、
これらのツールを使えば簡単です。大抵の整形ツールはオプションで一行あたり
の文字数を指定すれば、入力テキストを指定した文字数でそろえて出力してくれ
ます。

  でもちょっと待ってください。テキスト整形ツールの基本的な機能は、読み込
んだテキストを加工して出力するだけです。つまりフィルタです。という事は 
SED でも同じ事が出来そうですね。というわけでスクリプトです。


<it>FORM.SED</it>
<code>
  1 :loop
  2 $!{
  3   N
  4   /\n[ 　]*$/!b loop
  5 }
  6 s/[ 　]*\n[ 　]*//g
  7 s/[　-龠]/&!/g
  8 s/................................................../&\
  9 /g
 10 s/\([　-龠]\)\(\n\)!/\1!\2/g
 11 s/\(\n\)\([、。ー]!\)/\2\1/g
 12 s/\([　-龠]\)!/\1/g
</code>

  このスクリプトは、空行 (空白以外の文字が無い行) を段落の切れ目と判断し、
テキストを一行に半角 50 文字詰めに整形します。

  四行目は、パターンスペースの最後のセグメントが空行でないならば「:loop」
にジャンプします。つまり空行が現れるまで三行目の「N」が実行されるので、
パターンスペースには一段落が記憶されます。そして六行目以降の命令が実行さ
れます。

  さて、七行目は何をしているのでしょう。
正規表現「&lsqb;　-龠&rsqb;」は何を表すで
しょうか。「　」(全角空白, シフト JIS コードで 8140) は全角文字の最初の
文字です。そして「龠」は全角文字の最後の文字です (旧JIS では最後の文字で
すが、新JIS では「堯槇遙瑤」の四文字が追加されていますので、最後の文字は
「瑤」になります)。ということは「&lsqb;　-龠&rsqb;」は
任意の全角文字とマッチします
ね。置換文字列は「&amp;!」ですから、任意の全角文字を「&amp;!」に置換する...？

  実は置換文字列中の「&amp;」は、特別な意味を持っていまして、正規表現がマッ
チした文字列を表すのです。つまり次の置換命令と同じ意味です。

<verb>
  s/\([　-龠]\)/\1!/g
</verb>

  「&amp;」はマッチした文字列に、なにか文字列を加えたいとき便利です。この場
合でしたら任意の全角文字の後ろに「!」をつけるわけです。たとえばパターン
スペースが「SED 教室 第十回 」だったとしますと、この置換命令を実行した後
は、「SED 教!室! 第!十!回! 」になります。なるほど、でも一体何の為にこん
な置換をするのでしょう？

  みなさんもよくご存知の通り、ほとんどのパソコン/ワープロの画面上では、
全角文字は半角文字 2 文字分のスペースを占めます。従って、一行に半角 50 
文字詰めにするときは、全角文字を 2 文字分として数える必要があります。2 
文字分にするためには...もうおわかりですね。全角文字の後ろに「!」をつける
事により、全角文字を 2 文字分として扱おうという目論見です。

  さて次の置換命令は八〜九行目にわたっています。正規表現の部分には 50 個
の「.」が並んでいますから、任意の 50 文字にマッチします。マッチする文字
列が沢山ある場合は既に説明したように、一番左にある文字列を置換するのでし
た。つまり先頭から数えて 50 文字目までの文字列が
「&amp;&lt;改行&gt;」に置換される
のです。おっと、また「&amp;」が使われていますから、改行コードが 50 文字目の
次に追加されます。

  置換命令の最後に「g」がついている事に注意してください。「g」がつくとマッ
チする文字列が全て置換されるのでした。ただし一度置換された部分は置換しま
せんから、次の置換対象は 51 文字目以降の文字列です。従って 51 〜 100 文
字目が置換されます。つまり 100 文字目の次に改行コードが追加されるわけで
す。以下同様にしまして、50 文字毎に改行コードが挿入されます。

  十行目は、2 文字分の全角文字の間に改行コードが入ってしまったときの対策
です。十一行目は行頭禁則処理です。基本的な禁則しかしていませんが、応用は
容易だと思います。十二行目で、全角文字の後ろにつけた「!」を取り除きます。

  今回の SED 教室は大変短いですが、何分まだ論文が書けていないので (あと 
20 ページほど英文で書かなければならない。;_;) この辺で...

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第十一回 「正規表現、再論」
<p>
  SED のもっとも強力な機能である「タグ付き」正規表現は、SED 教室第七回で
紹介しました。
そのとき、「&bsol;1」は「&bsol;(」「&bsol;)」で記憶した文字列を呼び出すた
めの＊正規表現＊と紹介した事を覚えてますでしょうか。

  今までの SED 教室では「&bsol;1」は置換文字列中でしか使いませんでした。しか
し「&bsol;1」は正規表現ですから、当然正規表現文字列中で使うことが出来るのです。
たとえば次のような正規表現を考えます。

<verb>
  ^\(.*\)\1$
</verb>

  「.*」にマッチした文字列が「&bsol;1」で呼び出されます。つまり、「.*」にマッ
チした文字列と同じものが「&bsol;1」の位置に現れる時、この正規表現はパターンス
ペースにマッチするのです。たとえばパターンスペースが次の場合マッチします。

<verb>
  「abcabc」
</verb>

  「.*」に前半の「abc」がマッチする時、「&bsol;1」は「abc」と同じ意味になりま
す。従って「&bsol;1」は後半の「abc」にマッチするわけです。結局、正規表現
「^&bsol;(.*&bsol;)&bsol;1&dollar;」は
同じ二つの文字列からなる行にマッチしますね。たとえば次に
示すような行にです。

<code>
わんわん
にゃんにゃん
</code>

  「&bsol;1」が正規表現中で使えると説明しましたが、
もちろん「&bsol;2」,「&bsol;3」なども同様です。

  では次のスクリプトは、どんな動きをするでしょうか。

<it>PALIND.SED</it>
<code>
  1 h
  2 :loop
  3 s/^\(.\)\(.*\)\1$/\2/
  4 t loop
  5 /^$/b disp
  6 /^.$/b disp
  7 d
  8 :disp
  9 g
</code>

  三行目の置換命令で、正規表現中に「&bsol;1」が使われています。
「^&bsol;(.&bsol;)」ですか
らパターンスペースの最初の一文字が記憶されます。従って「&bsol;1」はその文字と
同じ意味になります。たとえばパターンスペースが「トマト」だったとすると、
「&bsol;1」は「ト」になります。「.*」は任意の文字列にマッチしますから、この場
合「マ」にマッチしますね。結局この置換によりパターンスペースの内容は「マ」
になります。つまり先頭の文字と末尾の文字が等しいときのみ置換が行われ、先
頭と末尾の文字を削除した文字列に置換されます。

  置換が行われたときは、四行目の命令「t」で二行目にジャンプして再び置換
が行われます。これを繰り返して置換が出来なくなったとき (つまり先頭の文字
と末尾の文字が異なるとき) 五〜六行目でパターンスペースの文字列の長さが 0 
または 1 の時のみ八行目にジャンプし、ホールドスペースに保存しておいた文
字列を標準出力に吐き出します。

  どんな文字列が吐き出されるかって？ ほとんど自明だと思いますが、例をあ
げておきましょう。

<code>
たけやぶやけた
しんぶんし
AKASAKA
</code>

  ほかにどんな文字列があったかな？ :-)

  さて、UNIX には「uniq」というコマンドがあります (多分 ASCII Tools にも
あります)。このコマンドは同一の行が連続する場合、それらを一つにまとめる
フィルタです。たとえば

<it>INPUT.TXT</it>
<code>
Apple
Apple
Apple


時計
</code>

  を入力すると

<code>
Apple

時計
</code>

  を出力します。この uniq コマンドの動作を SED で真似してみましょう。

<it>UNIQ.SED</it>
<code>
  1 $!N
  2 /^\(.*\)\n\1$/!P
  3 D
</code>

  一行目の「N」と三行目の「D」により、パターンスペースには常に標準入力の
連続する 2 行が保持されます。二行目の正規表現は、この 2 行が等しいときマッ
チします (「&bsol;n」はセグメントの区切りでしたね。)。条件に「!」があるのでマッ
チしなかったときのみ条件が成立し、最初のセグメントが標準出力に吐き出され
ます。つまり次の行と同じでない行のみを吐き出すのです。

  uniq コマンドには -c オプションを指定できます。指定すると連続する同一
行の行数を付加します。たとえば <it>INPUT.TXT</it> を入力した場合は次のよ
うな出力が得られます。

<code>
   3  Apple
   2  
   1  時計
</code>

  uniq -c は SED 教室第九回で紹介した、数字を 1 増やすテクニックを使えば
簡単に実現できます。

<it>UNIQC.SED</it>
<code>
  1 x
  2 1s/.*/    /
  3 H
  4 y/ 0123456789/11234567890/
  5 G
  6 s/.*\([^0]0*\)\n.*\n\(.*\)[^9]9*$/\2\1/
  7 x
  8 s/\n.*//
  9 $!N
 10 /^\(.*\)\n\1$/!{
 11   x
 12   G
 13   s/\n/  /
 14   P
 15   s/.*/    /
 16   x
 17 }
 18 D
</code>

  ほとんど SED 教室第九回で紹介した <it>NUMBER.SED</it> と同じですから簡
単ですね。

<it>UNIQC.SED</it>はいろいろな応用が出来ます。ここでは一例として英文の
単語毎の出現回数を数える方法を考えます。

  まず最初に英文から単語を切り出して、一行に一単語づつ並べます。スクリプ
トは次のようになるでしょう。

<it>VOC.SED</it>
<code>
  1 y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
  2 s/[^a-z][^a-z]*/\
  3 /g
  4 s/\n$//
  5 s/^\n//
  6 /^$/d
</code>

  たとえば次のような英文に対し、

<it>INPUT.TXT</it>
<code>
  A nondeterministic finite automaton is the essential concept of the
theory of formal language. It is also closely related to the coding
theory, and plays an important role in various applications. It is,
therefore, very important to investigate and understand the properties
of nondeterministic finite automata. In this thesis, the minimum
nondeterministic finite automata equivalent to the given automaton are
discussed, and the minimization algorithm is presented.
  < 以下略 >
</code>

<verb>
  sed -f voc.sed INPUT.TXT
</verb>

  を実行すると、次のような出力が得られます。

<code>
a
nondeterministic
finite
automaton
is
the
essential
concept
of
the
theory
of
formal
language
it
is
also
closely
  < 以下略 >
</code>

  さらにこの出力をアルファベット順に並べ換えます。並べ換えには sort コマ
ンドを使います。sort は元は UNIX のコマンドですが、MS-DOS 上で動く sort 
コマンドも各種配布されているようです。

<verb>
  sed -f voc.sed INPUT.TXT | sort
</verb>

  を実行すると次のように出力されます。

<code>
a
a
a
algorithm
algorithm
algorithm
algorithm
algorithm
all
all
all
all
also
also
always
always
an
  < 以下略 >
</code>

  これを UNIQC.SED に入力すれば単語毎の出現頻度を数える事が出来ます。さ
らに :-) その出力を sort すると出現頻度の高いものから順に並べる事が出来
ます。私の修士論文全文 (thesis.tex) を入力してみました。

<code>
A>sed -f voc.sed thesis.tex | sort | sed -f uniqc.sed | sort -r
 563  s
 535  the
 297  automaton
 291  of
 278  a
 224  is
 222  n
 203  in
 147  to
 121  and
 118  m
 114  states
 113  r
 106  delta
 103  i
  < 以下略 >
</code>

  「the」より出現頻度が高い単語が現れてしまった。三位に「automaton」が出
てくるのは論文のテーマだから当然かな？ なお sort の -r は逆順にソートす
るためのオプションです。ついでに言いますと、この出力の行数を数えると使用
単語数を知ることが出来ます。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第十二回 「〜から〜まで、基礎編」
<p>
  SED 教室の第三回目 (ずいぶん前ですね :-) で次の実行条件を紹介しました。

<descrip>
<tag>「実行条件１」,「実行条件２」</tag>
実行条件１が成立した行から実行条件２が成立する行までの間の行で成
立する実行条件。
</descrip>

  たとえば、次のスクリプトを考えます。

<it>TEST1.SED</it>
<code>
/これ/,/そして/p
d
</code>

  「/これ/」という実行条件が成立する行 (「これ」という文字列を含む行です
ね) から、「/そして/」という実行条件が成立する行まで、命令「p」を実行し
ます。スクリプト二行目の命令「d」の前には何も実行条件がついていませんか
ら、すべての行で「d」が実行されます。従って、「これ」を含む行から「そし
て」を含む行までは、「p」「d」が実行されるので標準出力に吐き出され、その
ほかの行は「d」のみが実行されるので、何も出力されません。

&num; SED の動き方を思い出しましたでしょうか？

  では、標準入力から次のファイルを読み込むことにします。

<it>INPUT.TXT</it>
<code>
これは一行目だ。
こっちは二行目。
そして三行目。
つづいて四行目。
これが五行目。
六行目。
最後の七行目。
</code>

  「/これ/」は <it>INPUT.TXT</it> の 1 行目と 5 行目に、「/そして/」は 3 
行目にマッチしますね。したがって次の文字列が標準出力に吐き出されます。

<code>
これは一行目だ。
こっちは二行目。
そして三行目。
これが五行目。
六行目。
最後の七行目。
</code>

  でも、ちょっと待ってください。1, 3, 5 行目は実行条件の中にマッチする条
件があるのですが、2, 6, 7 行目には「これ」という文字列も「そして」という
文字列も含まないので、条件の字面だけでは条件が成立しているかどうかの判定
が出来ませんよね。いったい SED はどうやって 2, 6, 7 行目を読んでいるとき
も条件が成立していることを知るのでしょう？

  答は単純です。SED が現在「/これ/,/そして/」という条件が成立しているこ
とを覚えているのです。SED は『「実行条件１」,「実行条件２」』という実行
条件がある行に対しては、以前に「実行条件１」が成立したかどうかを覚えてい
ます。以前に「実行条件１」が成立しているならば、条件『「実行条件１」,
「実行条件２」』も成立するわけです。そして「実行条件２」が成立する行に出
会うと「実行条件１」がかつて成立したということを忘れてしまいます。薄情で
すね。:-)

  まとめますと、

<quote>
  「実行条件１」,「実行条件２」
</quote>

  が成立するのは、

<quote>
「実行条件１」が成立する行か、または
「実行条件２」が成立する行か、または
かつて「実行条件１」が成立したということを SED が覚えている時です。こ
の時、「実行条件２」が成立するならば SED は以前に「実行条件１」が成立し
たということを忘れてしまいます。
</quote>

  では次のスクリプトはどうでしょう。

<it>TEST2.SED</it>
<code>
/これ/d
/これ/,/そして/p
d
</code>

  <it>TEST1.SED</it> と同様 <it>INPUT.TXT</it> の 1, 2, 3, 5, 6, 7 行目が出
力されると言いたいところだけど <it>TEST2.SED</it> の一行目によって「これ」
を含む行が削除されるから、2, 3, 6, 7 行目が出力されそうですね。ではやっ
てみましょう。

<code>
A>sed -f TEST2.SED INPUT.TXT

A>
</code>

  あれ？何も出力されない。困った。^^;)

  なぜ出力されないかと言うと、実行条件「/これ/,/そして/」が成立しないか
らです。一見 <it>INPUT.TXT</it> の 1 行目に「これ」という文字列があるので
条件が成立しそうですが、1 行目はスクリプト一行目で条件が成立し命令「d」
で削除されてしまうのです。したがって <it>INPUT.TXT</it> の 1 行目をスクリ
プトの二行目は読むことができず、その結果二行目の実行条件は成立しないので
す。

  このように SED はスクリプトの一行目から順に処理を進めて行くので、パター
ンスペースの内容が変化する場合や、命令「d」「b」などによってジャンプする
場合は注意が必要です。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>SED 教室 第十三回 「〜から〜まで、応用編」
<p>
  前回紹介したように、「/これ/,/そして/」などの実行条件を用いると、現在
条件が成立しているか否かを SED が覚えてくれます。したがってフラグの代わ
りに用いる事が出来ます。

  例えば、特定の漢字が、最初に現れた時だけ振り仮名をつけるスクリプトは次
のように書けます。

<it>YOMI.SED</it>
<code>
1,/条件/s//&(じょうけん)/
1,/教室/s//&(きょうしつ)/
1,/最初/s//&(さいしょ)/
</code>

  命令「s」の正規表現 (どの文字列を置換の対象にするか指定する表現でした
ね) がヌルストリング (長さ 0 の文字列) であることに注意して下さい。正規
表現が省略されると、直前の正規表現と同じであると解釈されます。つまり、

<verb>
  「1,/条件/s//&(じょうけん)/」は、
  「1,/条件/s/条件/&(じょうけん)/」
</verb>

と全く同じ意味です。長い正規表現を何回も書くのは面倒ですから、同じ正規表
現が続く時は省略した方が楽ですよね。でも楽なだけではありません、少しだけ
実行速度が速くなります。というのも、sed は実行時にまずスクリプトを全部読
み込んでから作業を開始するのですが、スクリプトに正規表現があると、それを
内部表現に変換します。正規表現が省略されていると、変換作業も省略できる 
(直前に作った内部表現をそのまま使えますからね) のでちょっぴり効率が良い
というわけです。

  さて、「1,/条件/」という実行条件は、標準入力の 1 行目から「条件」とい
う文字が現れる行まで成立します。つまり一度「条件」という文字列を含む行が
現れたが最後、この実行条件は以後二度と成立することはありません。従って置
換は最初の「条件」という文字列にたいしてのみ行なわれるわけです。

  文字列「教室」,「最初」についても同様です。この「SED 教室」のファイル
を <it>YOMI.SED</it> で処理して、どのように動作するか確かめて下さい。

  うまくいきましたでしょうか ?
  おや ? 2 つ目の「教室」にも振り仮名がついてる ?

<quote>
(注意) 「---------- SED 教室 第十三回 ... 」のタイトルが 
<em>1 行目</em>に来るようなファイルで実験して下さいね。(^^)
</quote>

  「1,/教室/」という実行条件は、1 行目から「教室」を含む行まで成立するわ
けですが、1 行目では「/教室/」という条件が成立するか否かは sed は関知し
ないのです。つまり最初の条件が成立した行以降で、2 番目の条件が成立する行
まで「〜から〜まで」の実行条件が成立する、というわけです。

  これを回避するには、1 行目を 2 回 sed に読み込ませれば OK です。すなわ
ち 1 回目は条件「1」を成立させるために、2 回目は条件「/教室/」が成り立つ
か調べるために。1 回目の読み込みでは「1 行目」でさえあれば何でもいいので、
パターンスペースをヌルストリングにすることにします。スクリプトは次のよう
に書けます。

<it>YOMI2.SED</it>
<code>
  1 1!b top
  2 h
  3 s/.*//
  4 :top
  5 1,/教室/s//&(きょうしつ)/
  6 1,/条件/s//&(じょうけん)/
  7 1,/最初/s//&(さいしょ)/
  8 1!b
  9 G
 10 /^\n./{
 11   g
 12   b top
 13 }
 14 s/\n.*//
</code>

  スクリプトの三行目でパターンスペースをヌルストリングにして 1 回目の読
み込みを行ないます。標準入力の「本当の」 1 行目は、スクリプトの二行目の 
「x」でホールドスペースに保存されています。そして十一行目で保存していた 
1 行目を元に戻して 2 回目の読み込みを行なうわけです。

  2 回目の読み込みでは、パターンスペースの内容とホールドスペースの内容は
同じです。従ってスクリプト十行目の実行条件は成立しないので、3 回目の読み
込みは行なわれず、標準入力の 2 行目の読み込みが行なわれますね。

<it>YOMI2.SED</it>では、最初に現れた文字列だけの置換でしたが、2 番目、
3 番目に現れた文字列を置換することも出来ます。

<it>YOMI3.SED</it>
<code>
  1 1!b top
  2 h
  3 s/.*//
  4 :top
  5 1,/\(条件\)\([^(]\)/s//\1(じょうけん)<1st>\2/
  6 1,//s//\1(じょうけん)<2nd>\2/
  7 1,//s//\1(じょうけん)<3rd>\2/
  8 1,//s//\1(じょうけん)<4th>\2/
  9 1,/\(最初\)\([^(]\)/s//\1(さいしょ)<1st>\2/
 10 1,//s//\1(さいしょ)<2nd>\2/
 11 1,//s//\1(さいしょ)<3rd>\2/
 12 1,//s//\1(さいしょ)<4th>\2/
 13 1!b
 14 G
 15 /^\n./{
 16   g
 17   b top
 18 }
 19 s/\n.*//
</code>

  正規表現の省略が多用されていることに注意して下さい。<it>YOMI2.SED</it>
では一度「条件」という文字列が現れた後は、「条件」という文字列は置換され
ませんが、<it>YOMI3.SED</it> だと、スクリプト六〜八行目があるので、2 番め
以降の「条件」という文字列も置換されます。文字列「条件」が現れるたびにス
クリプト六〜八行目の実行条件が順に不成立になっていくわけです。

  あと注意すべき点は、置換後の文字列「条件(じょうけん)&lt;1st&gt;」の中に置換
対象の文字列「条件」が含まれてしまっている点です。

&num; 読み仮名をつけるという目的上、やむを得ませんね。

  <it>YOMI2.SED</it> と同じように「s/条件/&amp;(じょうけん)/」で置換すると、
最初の文字列「条件」が何回も置換されて、

<verb>
  条件(じょうけん)<1st>
      ↓
  条件(じょうけん)<2nd>(じょうけん)<1st>
      ↓
  条件(じょうけん)<3rd>(じょうけん)<2nd>(じょうけん)<1st>
</verb>

  などとなってしまいます。<it>YOMI3.SED</it> の様に、
正規表現に「&lsqb;^(&rsqb;」を
付けて、文字列「条件」の後に「(」が続かないもののみを置換対象にすれば、
一応 (^^;) 防ぐことが出来ます。

<code></code>
GCD03723 (Greatest Common Divisor:最大公約数)

<sect>Readmeより<label id="readme">
<p>

<sect1>Release 1.11 について
<p>
  早いもので、「SED 教室 第一回」
を書いてから約四年になります。当時私が
使ってたマシンは、元祖 Dynabook (J-3100SS001) でした。このマシンは 8086
10MHz で、最近のマシンと比べると 1/100 以下の性能です。まさに sed がふさ
わしいマシンでした。複雑な処理をなんとか sed を使って実現できないかと、
いろいろ考えたものです。

  しかし年々マシンの性能は向上し、sed の必要性はどんどんなくなっていきま
した。私自身、現在は sed で複雑なスクリプトを書くことはほとんどありませ
ん。普通のパソコンで perl が軽々動くのですから、どうして無理に sed を使
う必要があるでしょうか :-)。

  とはいっても、Release 1.00 公開後に書いた「第十二回」「第十三回」をそ
のまま放っておくわけにもいかず、また「第十四回」を書くのはいつになるかわ
からない (果たして書くことがあるだろうか ^^;) ので、「第十三回」までを 
Release 1.11 としてまとめておくことにしました。perl には sed の機能が取
り込まれていますから、現在でもこの SED 教室がお役に立てることがきっとあ
るでしょう (と願いたい :-)。

  Release 1.11 公開にあたり再配布条件を大幅に変更しました。Release 1.00 
では、商用 BBS 等に転載する際は転載者に、ユーザと著者を結ぶ双方向の連絡
役をお願いしていました。ところが今では大抵の BBS が Internet に接続され、
誰でも容易に著作者に電子メールを出すことが出来るようになったので、連絡役
が必要でなくなりました。そこで、Release 1.11 は GNU 一般公有使用許諾に従っ
て再頒布/変更可能としました。

  なお、著者への連絡は、sengoku@gcd.org 宛でお願いします。すでに 
Nifty は退会しましたので Nifty の ID である GCD03723 宛にメールを送ると、
読むことができないばかりか、現在この ID を使用中の方に迷惑となりますので、
ご注意願います。
<quote>
<url url="http://www.gcd.org/sengoku/Welcome.ja.html"
name="著者のホームページ">
<newline>
<url url="http://www.gcd.org/sengoku/sedlec/"
name="SED教室ホームページ">
</quote>

<sect1>謝辞
<p>
  SED 教室の為に会議室を作ってくださった FMCN フォーラムの SysOp 
☆れお☆さん、および SED 教室に対して多くの励まし・質問を書き込
んでくださった 	FMCN フォーラムのみなさんに感謝いたします。

<sect1>著作権
<p>
  この SED 教室パッケージに関する全ての著作権は、原著作者である
仙石浩明 (sengoku@gcd.org) が所有します。この
パッケージは free software です。Free Software Foundation が公表
した GNU 一般公有使用許諾のいずれかの version を選択し、それが定
める条項に従って本パッケージを再頒布または変更することができます。

  本パッケージは完全に無保証です。詳細については GNU 一般公有使
用許諾書をお読み下さい。もし、GNU 一般公有使用許諾書をお持ちでな
い場合は、Free Software Foundation, Inc. 675 Mass Ave,
Cambridge, MA 02139, U.S.A. へ手紙を書いて下さい。

<sect1>SGML版について
<p>
SED 教室 SGML版は遠藤 康裕 (quiver@tky3.3web.ne.jp)が
オリジナルのテキスト版をLinuxdoc-SGMLで扱う事ができるように
修正しました。
本文中、「第n回」という表記が度々登場します。
このような手動での番号付けはSGMLの
本来の意義に相反するものと思われますが
可能な限りオリジナルの文章を変えないことを
優先して「第n回」という表記を残すことにしました。

SGML版のオリジナルのテキスト版にない部分
(このコメント以外はほとんどはSGMLタグの埋め込み)の
著作権は遠藤 康裕 (quiver@tky3.3web.ne.jp)が所有します。

SGML版「SED教室」は完全に無保証です。GNU 一般公有使用許諾の
いずれかの version を選択し、それが定める条項に従って
本パッケージを再頒布または変更することができます。

</article>
