---------- SED 教室 第七回 「タグ付き正規表現」 ---------- 前回、複数行を加工する方法について説明しましたが、肝心の加工自体は不要 な文字を命令「s」で消したり他の文字に置き換えると言った比較的単純なもの に限られていました。 したがって会議室の発言のログから次のようなタイトル一覧 (RT コマンドで 表示されます) を作ろうとすると困ってしまいます。 <タイトル一覧> 番号 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 なぜなら、発言のタイトルは次のような形式になっているからです。 >003/009 GCD03723 最大公約数 SED 教室その 1 >(17) 91/12/07 08:02 コメント数:1 タイトルが 2 行にわたっているのは前回の複数行を扱う命令「N」でなんとか なるとして、問題は並んでいる文字の順番です。つまりタイトルでは、番号、ID、 題名、登録した日付、コメントの有無の順なのですが、タイトル一覧では題名が 最後になっています。 今までの方法ですと、タイトル一覧では ID の次に登録日付が来ますから、ID の後から日付の前までを消去する事になりますが、そうすると題名も一緒に消 えてしまいます。なんとか消す前に題名を覚えておけばうまく行きそうです。と いうわけで新しい正規表現の紹介です。 \(正規表現\) 「正規表現」にマッチした文字列を記憶します。 物事を忘れるという事は脳ミソから記憶した事柄がなくなるのではなくて、事 柄自体は残っているのだけれども、その呼出が出来なくなるだけだという説があ りますが、それと同じでいくら記憶したと言い張っても、呼び出す方法がなけれ ばナンセンスです。したがって呼び出すための正規表現があります。 \1 正規表現の一番左にある「\(」と対応する「\)」で囲まれた部分にマッ チする文字列を呼び出します。 \2 正規表現の左から2番目にある「\(」と対応する「\)」で囲まれた部分 にマッチする文字列を呼び出します。 以下同様で、 \3 \4 … \9 まであります。このような記憶と呼出が出来る機 能を持つ正規表現を「タグ付き正規表現」と呼んで、これらの機能を持たない正 規表現と区別する事があります。「タグ付き」のものとそうでないものとは、能 力的には大違いで、後者が通常の意味での正規表現で、表現する文字列を有限オ ートマトンで認識できるのに対し、前者はチューリングマシンでしか認識できな い文字列集合を表現する事が出来ます。また余計な事を話してしまいました ^^;) が、要するにこの「タグ」が付く事によって、はるかに多様な文字列が表現出 来るようになっているという事です。 さて、「\(正規表現\)」は「正規表現」がマッチする文字列とマッチします。 たとえば文字列「abcdefg」は正規表現「abc\([a-z]*\)g」にマッチします。つ まり、マッチするかどうかの判定に関しては、「\(」と「\)」は何の影響も及ぼ しません。今までの正規表現と異なるのは、「\(」と「\)」で囲まれた部分にマ ッチする文字列を記憶し、「\1」でその記憶を呼び起こす事が出来る点にありま す。すなわちこの場合でしたら、「\1」は「def」と同じ意味になります。ここ で注意しなければならないのは「\1」が意味する文字列は「\([a-z]*\)」がマッ チする文字列に従って変化する事です。 さらに \1 … \9 の正規表現は、置換命令「s」の置換文字列中に用いる事が 可能なのです。たとえば「s/^\(.\)\(.\)/\2\1/」などと出来ます。この置換命 令の正規表現から「\(」と「\)」を取り除くと、「^..」になりますから先頭の 任意の二文字にマッチすることがわかります。最初の「.」は一文字目、次の「.」 は二文字目にマッチするので、「\(」と「\)」をそれぞれにつけることによって、 「\1」に一文字目、「\2」に二文字目が記憶されます。 置換文字列は「\2\1」ですので、「\2」に記憶された文字列 (二文字目) と 「\1」に記憶された文字列 (一文字目) を続けた文字列 (二文字を逆順にしたも の) で置換されるわけです。結局この置換によって最初の二文字が逆順になりま す。 「\(」と「\)」は、使い方が慣れるまではちょっと難しいでしょうから、もう ちょっと練習してみましょう。 パターンスペースが「abcdefgVWXYZ0123」の時、 命令「s/\([a-z]*\)\([A-Z]*\)/\2 \1/」を実行するとパターンスペースの内 容はどうなるでしょうか。 「\(」と「\)」を取り除いて考えると、「[a-z]*[A-Z]*」ですから英小文字 (「[a-z]」) の 0 回以上の繰り返し (「*」) と英大文字 (「[A-Z]」) の 0 回 以上の繰り返しがつながったものにマッチします。従って「[a-z]*」は 「abcdefg」、「[A-Z]*」は「VWXYZ」にそれぞれマッチします。故に「\1」は 「abcdefg」、「\2」は「VWXYZ」となり、置換文字列は「VWXYZ abcdefg」にな ります。 その結果、置換後のパターンスペースは「VWXYZ abcdefg0123」になるわけで す。 この辺は習うより慣れる方が速いので、いろいろと実験してみてください。 次のスクリプトは、会議室の発言のログファイルを読んでタイトル一覧を出力 します。タイトルを加工するために沢山の命令「s」とタグ付き正規表現を使っ ています。複雑ですが、命令「s」の前後でパターンスペースの内容がどのよう に変化するか、命令「p」を挿入して調べれば、理解する事はそう難しくはない でしょう。 <<< RT.SED >>> --------------------------------------- 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/ --------------------------------------- おっと、まだ紹介していない命令がありました。 b ラベル 「:ラベル」がある行へジャンプ。 :ラベル ジャンプ先。 たとえば「b abc」という命令を実行すると、「:abc」と書かれている行へジ ャンプします。 ちょっと難しいかも知れませんね ^_^;)。ヒントを書きましょう。十~十一行 目の命令「s」(十行目の行末に「\」がある事に注意して下さい、置換文字列中 に改行コードを入れたいときはこうするのでした) を実行する前のパターンスペ ースの内容が例えば次のようだったとします。 --------------------------------------- 3 GCD03723 SED 教室その 1 (17) 91/12/07 08:02 コメント数:1 --------------------------------------- s/^\(.*\n.*\n\)[ ()0-9]*\/\([0-9/]* [0-9:]*\) */\1\2\ / を実行すると、「 3 GCD03723<改行>SED 教室その 1<改行>」が 「^\(.*\n.*\n\)」にマッチします。そして「(17) 91/」が「[ ()0-9]*\/」、 「\([0-9/]* [0-9:]*\)」が「12/07 08:02」にマッチして、「 *」は「コメント」 の直前までの沢山の空白にマッチするわけです。従って、命令実行後のパターン スペースは --------------------------------------- 3 GCD03723 SED 教室その 1 12/07 08:02 コメント数:1 --------------------------------------- となります。 もうちょっと詳しく説明したかったのですが、残念ながら紙数が尽きてしま (うわけないか ^^;) いました。もしわかりにくいところがありましたら、質問 してください。 --- GCD03723 (Greatest Common Divisor:最大公約数)