て、まったく同じことがて、き , grep -v ab*c' filel は , sed /ab * c/d filel るのは # 以外の文字て、始まる行て、す。と考え # て、始まる行と空行が削除されると残され ・ # で始まる行と空行を削除する が簡単て、す。 sed / #/d file 単に削除コマンドを使うだけて、す。 ・ # で始まる行を削除する それて、は , sed の利用例て、す。 合は多々あります。 ることは grep て、行ったほうが手軽て、ある場 したほうがいい」とは違います。 grep て、て、き す。もちろん , 「て、きる」ということは「そう sed を使った処理に置き換えることがて、きま 同じて、すから , grep を使った処理はすべて sed の正規表現と grep の正規表現はほとんど て、まったく同じことがて、きます。そのうえ , s/%%[A-Za-z] CA-Za-z]*/{cÖmmand}/g comment: List sed のスクリプト とまったく同じ動作をします。 cat ファイル名 . これは , sed -n /A/P ファイル名 . ・意味のない使い方 g フラグを忘れないことが大切て、す。 sed ' s/## * /#/g file 置換コマンドを使うだけて、す。 き換える ・複数の # の連続した並びをひとつの # に置 て、うまくいきます。 sed -n 厂 [ #]/p file ると , 54 C MAGAZI N E 1993 3 だ 0 ! 9A ー Za ー幻 * $ / d s/{[0-9]C0-9]*[A-Za-z][A-Za-z]}/{length}/g s / { ー [ 0 ー 9 ] [0-9]*CA-Za-z] CA-Za-z]}/{length}/g 正規表現とは関係ありませんが , sed -n ' 1 , $p ファイル名 . awk awk のコマンドラインからの使用の基本 は次のとおりて、す。 awk プログラムファイル名 . プログラムがややこしくなったときには , 適当なファイルに書き込んて、おいて , awk -f プログラムファイル名ファイ ル名 . として呼び出すことがて、きます。なお , コ マンド名は awk て、はなく , nawk(awk の新 バージョンの場合 ) や gawk(GNU 版の awk の場合 ) になることもあります。 awk のプログラムは , バターン { アクション } パターン { アクション } の形をしています。 awk はテキストを 1 行ず つ翫み込み , 各パターンごとにそのパター ンを満たすかどうかを調べ , 満たせば対応 するアクションを実行します。 パターンにはいろいろな式が書け , / 正規表現 / の形のものを書くこともて、きます。この場 合 , その正規表現にマッチする文字列がそ の行に含まれるという条件を表します。ま た , 文字列が正規表現にマッチするか判定 する演算子もあるのて、 , 正規表現を使った cat ファイル名 . とまったく同じ動作をします。 条件式を書くことも可能て、す。これは , パ ターンの中て、もアクションの中て、も使えま す。 egrep て、て、きることはすべて awk て、もて、き ます。たとえば , egrep ab*c filel は , awk /ab*c/print' filel て、まったく同じことがて、き , grep -v ab*c' filel は , awk ' !/ab * c/print filel て、まったく同じことがて、きます。そのうえ , awk の正規表現と egrep の正規表現はほとん ど同じて、すから , egrep を使った処理はすべ て awk を使った処理に置き換えることがて、 きます。 しかし , 「て、きる」ということは「そうした ほうがいい」とは違うのて、 , egrep て、て、きる ことは egrep て、行ったほうが手軽て、あること は , sed と grep の関係と同じて、す。 awk のプログラム例を示します。 ・ / h 。 me 直下のユーザー欄 UNIX のノヾスワードファイルからホームデ ィレクトリが / home の直下にあるユーザの ューザ名を取り出す。 BEGIN{FS=' sed のスクリプトや awk のプログラムにつ いて解説を始ると本が一冊書けてしまうほ ど奧深いのて、すが , 誌面の都合て、割愛しま す。興味のある方は , 関連書籍をぜひご 読ください awk のプログラム LiSt BEGIN{FS= ” {username = spIit($6,fnames, ” /つ; POS if (username ! = fnames[pos]) { pr i n t
高効率テキスト処理を実現する 特 0 集 正規表現の にめ イントロダクション はじめに 、 UNIX で広く普鷹している正規表 現も MS - DOS の世界ではあまり 活用されていない。確かにメタキ ャラクタと呼ばれる文字を駆 使しての謎に手を焼くこともあ るが , 正規表現の持つ強力な文字 列検索や置換機能を活用すれば , 正規表現とは 膨大なデータでさえきめ細かく , しかも極めて効率的にできる。 さらに正規表現の修得は , sed や awk などに代表される , いわゆる フィルタ系言語理解への道をも拓 く。槫集では正規表現理解とと もに , その実践的な活用を探る。 本誌の読者なら「正規表現」という言葉を 一度は目にしたことがあるてしよう。これ まてに , grep, sqd, awk, perl などの , ツ ールやフィルタ系言語が記事て、紹介されま したが , そのたびに 「正規表現を用いたパターンマッチ」 38 C MAGAZINE 1993 3 なるフレーズが出てきて , のような呪文にお目にかかると , 何のこと やらさつばりわからなくなってしまった方 も少なくないのて、はないて、しようか。 あるいは逆に , 読者の中には , ふだんの 仕事や生活の中て , 通信のログを管理した り , 電子化した住所録を検索したりするの に , grep や awk を縦横無尽に使っていて , 正規表現にはすっかり慣れ親しんているパ 堀江郁弥 るパターンを表現する方法てす。 正規表現は , 正規文法によって生成され 正規表現って ? て、す。 っとまじめにとりくんて、みようというもの 本特集は , この正規表現について , ちょ ワフルな方もおいて、て、しよう。
単なアウトラインドキュメントの後処理ス クリプトを紹介します。この awk スクリプ トは私が実際に卒業論文の仕上げて、使用し たものてす。 長い文章を章だてするときに , 章番号を いちいち書き始めから数字て、記入するとい うのは非現実的て、す。そこて、 , 章や節 , 項 という単位ごとに > ←章のレベノレ > > ←節のレベル > > > ←項のレベル という印だけを見出しの前につけることて、 , それが章・節・項の見出して、あることを表 し , それが先頭から数えて何番目かという のは awk に数えさせようというものて、す。 1.1 設計方針 1 概説 という入力を , > > 設計方針 > 概説 実際には , 62 C MAGAZINE 1993 3 関数て使えますが , よく使うところては , 換操作が可能て、す。いろいろな文字列操作 awk の関数においても正規表現を使った置 また , 入力パターンの選択だけて、なく , 2 〃などに順次置き換えていきます。 実行され , 、、 > > > 〃といった文字列を、、 1.1. この行を受け付けると , { } の中の処理が 響をおよばさないのて、す。 たがって , 見出しの文字列は正規表現に影 続いているかはまったく気にしません。し このパターンは、、 > 〃の繰り返しの後に何が が , 続く { } の中に記述されているのて、す。 にマッチする。このような行に対する処理 し 行頭にある、、 > ′ ' のひとつ以上の繰り返 に伝えるのて、す。このパターンは , パターンを示す正規表現て、あることを awk て、す。これを、アて、挟むことて、 , これが検索 > 十 めの正規表現は , , こて、使っている行バターンの検索のた と加工して標準出力に出力するものて、す。 gsub( ) があります。この gsub ( ) は前出のスクリプ トて、も使用されていますが , 機能は文字列 中のパターンにマッチする部分を別の文字 列に置換するというものて、す。スクリプト 中て、は文字列の最後の余分な、、 . 〃を消すため に を長さ 0 の文字列 C っと置き換えています。 、、 \ クによってエスケープしておかないと , がメタキャラクタとして働いてしまい , 文 字列の末尾の文字すべてにマッチしてしま います。 こて、も正規表現て、あることを示すため にソ〃て、挟まなければなりません。 さて , 今度は逆に番号を振ったアウトラ インから見出しの行だけを抽出してみまし よう。どういうパターンを指定すればいい て、しようか ? 数字と、、 . クの繰り返して、始まる行が見出し と表現します。 続く部分は , そうするとパターン 2 ・ 3 て、 て、あるとして , としてもかまわないのて、すが , 現は , . ( まさに、、 . ″のみの行 ) . 9.10.2. この正規表 のような見出し番号として生成されないよ うな行にもマッチしてしまいます。これを 回避するには , 見出し番号のパターンをよ く見て適切な正規表現を与えなければなり ません。 見出しの番号は , ノヾターン 1 パターン 2 バターン 3 といった形て、す。 ヾターン 1 とパターン 2 ・ 10 10 . 1 10 . 1 . 1 3 て、場合分けをしたほうが見通しがよくなる ことがわかるて、しようか。パターン 1 には が含まれておらず , パターン 2 ・ 3 はパター ン 1 の形に 数字 が繰り返し続いているのて、す。したがって , まずパターン 1 を数字のみの文字列として , と表現て、きます。これを , 工テイタで使う正規表現 な対話型のアプリケーションて、どれだけ正 現をサポートしています。工デイタのよう パソコンのエデイタて、は MIFES が正規表 断て、きます。 はなく , 単なる文字として扱っていると判 その処理系は丸カッコをメタキャラクタて、 列の含まれる行のみが出力された場合は , クタて、す。しかし , 本当に、、 ( ) クという文字 チしているわけて、 , 丸カッコはメタキャラ たパターン , つまり長さ 0 のパターンにマッ が出力されるのならば , 丸カッコて、囲まれ えてみればわかります。これて、すべての行 などとして , 、、 ( ) 〃だけの検索パターンを与 A> jgawk " / ( )/{print $ の " *. c A > grep ( ) * . c がメタキャラクタかどうかを確認するには , その処理系において , たとえば丸カッコ メタキャラクタかどうかの見分け方 とします。 */{print $ 0 } " 日 LENAME A> jgawk " だ [ 0 ー 9 ] 十 ( \. [ 0 ー 9 ] + ) これを awk て、実行するときには , という表現をしているのて、す。 (pattern) * が 0 個以上ということて、 , として扱うことがて、きます。このかたまり ッコて、囲むと , それをひとまとまりのもの 2 にあたるわけて、す。パターンの連続を丸カ となります。 ( ) て、囲まれた部分がパターン る正規表現のて、きあがりて、す。これは , として組み合わせれば見出し番号を網羅す 1 と任意個のパターン 2 行頭から始まる文字列で 1 個のパターン
grep コマンドは , ラインエデイタ ed をも とに作られました。 ed から正規表現による 検索の機能だけ取り出してフィルタにした コマンドといってもいいて、しよう。そのた め , 使える正規表現は ed とほとんど同じて、 す。それは , あまり強力なものて、はありま せん。また , grep はマッチングのアルゴリ ズムも ed と同じものを使っています。正規 表現の定義をほとんど素直に実現した遅い アルゴリズムて、す。 そこて、 , 改良版として egrep コマンドが作 られました oegrep は grep よりも利用て、きる 正規表現が大きく拡張されています。また , マッチングのアルゴリズムも , 決定性有限 オートマトンに変換してから実行するもの を使っています。そういうわけて、 , egrep は grep よりも速くて便利て、す。筆者はほとん ど egrep ばかり使っていて , grep を使うこと はめったにありません。 しかし , 今なお , 世の中から grep はなく なっていません。 grep と egrep は共存してい ます。なぜて、しようか。実は , egrep は gre p に上位互換て、ないからて、す。 grep の正規表 現はそのままて、は egrep の正規表現にならな ことがあります。そのため , grep コマン ドを下請けに使っているプログラムのため に , grep コマンドを残しておく必要がある のて、す (UNIX には , 同様の理由て、残ってい るコマンドがいくっかあります ) 。 さらに , 機能的にも egrep は grep に上位互 換て、はありません。 grep て、て、きることはほ とんど , egrep を使ってもて、きるのて、すが , ひとつだけ grep にて、きて egrep にはて、きない ことがあるのて、す。そのためにも , grep を 捨て去ってしまうことがて、きないのて、す。 grep て、しかて、きないことが何て、あるかは , 後ほど具体的な利用法を説明するときに説 明します。 かっては grep を捨て去ることのて、きない 理由がもうひとつありました oegrep は正規 表現を決定性有限オートマトンに変換する のて、 , 最初にちょっと待たされます。だか C MAGAZINE 1993 3 ら , 小さな入力に対しては grep のほうが有 44 利だというものて、す。 確かにそのとおりなのて、すが , いまはコ ータも以前よりずっと速くなりまし あまり意味はないて、しよう。小さ な入力に対しては , どちらて、検索を実行し ようと一瞬に結果が出るのて、あまり差は感 たから , ンヒ。ュ ていると見ることがて、きます。 プリタが実行して , テキスト処理が行われ 語て書いたプログラムを , sed というインタ 工デイタコマンドというプログラミング言 ラミング言語て、あるともいえます。つまり , す。これは , 見方によると , 一種のプログ マンドに従ってテキスト処理を行うものて、 sed は , あらかじめ与えられたエデイタコ す ( て、きなくはないのて、すが・・・ て何かしようとするとけっこうたいへんて、 処理するのて、 , 以前に入力した行を利用し sed は基本的にテキストを 1 行ずつ入力して に前後に動いて処理することがて、きますが , を入力して処理するのて、テキスト中を自由 ます。 ed はファイルからまとめてテキスト にしたことて、犠牲になっている機能もあり 簡単になりました。その代わり , フィルタ ほかのコマンドと組み合わせて使うことが いて、しよう。フィルタにしたことによって , 書き込みむようにしたのが sed だといってい キストを読み込み , 編集して , 標準出力に を改造してファイルまたは標準入力からテ 編集して , ファイルに書き込みます。これ なのて、ファイルからテキストを読み込み , たものといえます。 ed はテキストエデイタ sed コマンドは ed そのものをフィルタにし されたそうてす。 もありません。 sed コマンドがて、きて , 駆逐 いたことがあります。しかし , 今は影も形 します。実際 , 昔は存在したという話を聞 したコマンドというものがありそうな気が よる置換の機能だけ取り出してフィルタに 検索の次は置換て、す。 ed から正規表現に (sed, awk など ) ストリームテキスト処理 じないて、しよう。 しかし , 工デイタコマンドて、複雑なプロ グラムを書くのはたいへんて、す ( たいへんて、 ないと感じるハッカーも世の中にはいます が ) 。そこて、 , もっと普通の手続き型言語に 近い形のプログラミング言語を使うインタ プリタを作ってはどうだろうというアイデ ィアが生まれます。それを実現したのが aw k て、す。 awk て、使える正規表現は egrep とほとんど 同じて、す。 sed て、使える正規表現は grep とほ とんど同じものて、すから , 正規表現だけ取 っても , awk のほうが sed よりも便利になっ ています。 一般に sed て、て、きることは awk て、もて、きま す。て、も , awk があれば sed は不要というこ とはありません。ちょっとした置換を行う だけなら , sed のほうが手軽にて、きるという こともありますから・・ プログラムの中て、正規表現を簡単に扱う ためのライプラリも , 最近て、はいろいろ使 えるようになりました。たとえば , UNIX の 一部のノヾーションにある regex ライプラリや GNU の libg 十十の Regex クラスなどがそれ て、す。ちなみに筆者は Regex クラスを愛用し ています。 これらのライプラリの多くは , 正規表現 を適当な内部表現 ( ライプラリによって決定 性有限オートマトンの場合も単なる木構造 の場合もある ) にコンパイルする部分とコン パイル結果を利用してマッチングの判定を 行う部分からなります。したがって , マッ チングの対象となる正規表現が実行時に決 まるときには , これらのライプラリを使う といいて、しよう。 一方 , コンパイラなどのよラにどんな正 規表現を使うかが初めからはっきりとわか っているときは , これらのライプラリを使 うよりも , 字句解析系生成系の使用をおす すめします。 ライプラリ
特集①正規表現のすすめ① のような文字列にマッチしてしまいます。 、、〃は忘れないようにしてください また , 名前とカッコの間に任意個のスペ ースを許すために , 「スペースの繰り返し」 を表す正規表現が必要て、す。正規表現その ものとしては , スペースはスペースとして 入力してかまいません。ところが , MS-DO S て、 grep を command.com から使うときに コマンドインタブリタがスペースの前後を 別の引数として grep に渡してしまうと , / ヾターン Ca-zA-Z ] [a-zA-Z0-9 ] * ファイル名 * (. * ) と grep が解釈してしまい , 「スペースの任意 個の繰り返し」という目的が達成て、きないの て、 , ダブルクオートて、パターン全体を囲ま なければなりません (MS-DOS の COMMA ND.COM の場合 )。また , ューティリティに よってはスペースが正規表現の一部として 直接指定て、きないものもあります。その場 合だいたいは、、 \ s 〃という指定が 1 文字のスペ ースを意味するようて、す。 念のため , 名前とカッコの間にスペース だけて、なくタブも許すように修正しましよ う。スペースまたはタブの任意の長さの連 続というのは , という正規表現て、表されます。したがって , 前述の正規表現は , となります。ちなみにこの正規表現は C の関 数の形にマッチしますが , 関数の呼び出し なのか , 宣言なのか , あるいは定義なのか はまったく区別て、きない正規表現て、す。 次の例に移りましよう。 int もしくは long の関数の宣言はどのように正規表現て、表す ことがて、きるて、しようか。関数の宣言が常 に行頭から始まると仮定してよいならば , (int\ long) [ *t] 十 [a-zA-Z ] [a-z 抽出する g 「 ep の例 て扱うことがて、きます。 awk (jgawk) は、、 ( ) 〃をメタキャラクタとし て機能するものて、しか使えません。後述の め , 、、 ( ) 〃が正規表現のメタキャラクタとし 「 int または long 」を検索する必要があるた といった具合になります。ただし , これは grep はその正規表現にマッチする ( あるい List 抽出して加工する awk のプログラム # > 〉〉から見出し番号を作る ( 6 段階に限定している ) j く len for ( j counterCIen len length($l) = x sprintf("%d. X pr i n t $ 0 〉〉” mo k uj i. i dx for (j len counter[j] pr i れ t counterCj]); はマッチしない ) 行を抽出します。つまりそ の文字列を含む行がそのまま標準出力から こて、注意すべきは , パ 得られるのて、す。 ターンマッチの単位が行て、あることと , 出 カ時に加工を施されないことて、す。 grep て、は検索のための正規表現ととも に , オプションスイッチを併用するとパタ ーンの記述て、苦労せずに済みます。たとえ ば , 大文字小文字を無視するオプションの 、、一 i 〃を指定すると , パターン文字列が大文字 にも小文字にもマッチするようになります。 つまり , A> grep -i finger target. txt とすると , パターン、、 finger クは , finger Finger FINGER fingER といった文字列にマッチするのて、す。これ を正規表現て、表そうとすると , ちょっとや っかいて、す。、、 [ ] 〃を使って , 、、 finger" の 1 文 字ごとに大文字と小文字のそれぞれを受け 付けるような正規表現として , [Ff] [ ⅱ ] [Nn] [Gg] [Ee] [Rr] という表現がて、きますが見てのとおり、、一 i 〃 を使ったほうが簡単て、す。 パターンに合致しない行だけを出 力するオプションもあります。、、一 v 〃て、す。 これはつまり不要な行のパターンがわかれ ば必要な行のパターンを網羅する必要がな いということて、す。 抽出して加工する awk の例 awk はパターンドリプンとて、もいうべき 処理系て、す。パターンごとの処理が定義て きるため , パターンごとに入力を処理する ことがて、きます。このパターンに基づく処 特集正規表現のすすめ この機能を利用した例として ,List 8 に簡 .. ( 処理内容 ).. /pattern/ { 理は次のような構文て、指定します。 61
口を返します。 ほかにも String クラスと Regex クラスを組 み合わせて使う方法は存在します。 C 十十の 書ける人はぜひ libg 十十のマニュアルをじっ くり読んて , Regex クラスを使いこなしてく ださい 形式言語に関する教科書をお読みください 理論的な話に関しては , オートマトンと す。 方のために , 参考文献をいくっか紹介しま 正規表現に関してもっと詳しく知りたい 参考文献 多数あるもののなかから , 手元にあるもの をあげておきます。 Part [ 3 ] コンパイラ , 中田育男著 , 1981 , のて、 , 手元にあるものをあげますと , コンパイラの教科書もたくさんあります ンス社 山崎秀記共訳 , 1984 , 1986 , サイエ 著 , 野崎昭弘 , 高橋正子 , 町田元 , J. ホップクロフト , J. ウルマン共 [ 2 ] オートマトン言語理論計算論 I , Ⅱ , 著 , 1983 , 共立出版 礎講座 11 ) , 小林孝次郎 , 高橋正子共 [ 1 ] オートマトンの理論 ( 電子計算機基 産業図書 を読むのがいちばん便利のようて、す。 libg 十十については今のところマニュアル 出版局 Dougherty%, 福崎俊博訳 , アスキー sed&awk70 ログラミング , Dale [ 5 ] sed と awk についてはやはりこれて、しよ ウルマン , 1990 , サイエンス社 Ⅱ , A. V. 工イホ , R. セシイ , J. D. [ 4 ] コンパイラ原理・技法・ツール I , 正規表現プログラミング概説 正規表現をプログラムに応用するにはどうした らいいか , 難解な部分をプログラミング上で回 避するコツを実際のツールや GNIJ ライプラリ 「 regex 」を参考に解説します。 正規表現の実用化 はじめに 58 C MAGAZINE 1 3 3 まず頭に浮かぶのが UNIX からの移植ツー ティて、しようか ? 受けることがて、きるのはどんなユーティリ MS-DOS 環境において正規表現の恩恵を 受けることがて、きません。 ーティリティて、なければ正規表現の恩恵を いうまて、もなく正規表現を受け付けるユ 実際に正規表現を使ってみましよう。 ルて、す。 UNIX 上て、正規表現が使えたものは MS-DOS て、もほば同様に使用て、きます ( たと えば wc などは UNIX 上て、は正規表現とは無 に立つかについて説明します。 er. 5 を例にあげ , どのように正規表現が役 本節て、は grep, awk (jgawk) , MIFES V て、す。 プロて、検索文字列の指定に使用て、きるよう UNIX 関係以外て、は一部のエデイタやワー 現とは無関係て、ある ) 。 関係なのて、 , MS-DOS 版て、もやはり正規表 まず当然のことながら , メタキャラクタ TabIe 8 「ます $ 」のマッチング マッチする 文字列 マッチしない 文字列 川で泳きます 山に登ります ます 山に登ります。 山に登りますか
て、ログを取っておきたい というめんどう な人が多いのて、ある。私の場合は , プログ ラマーズフォーラムくらいは , 会議室ごと に毎月 , 別ファイルにしてログを保存して いる。このときにどのようなファイル名に するかは , 個人の流儀次第だから深入りし 今回のテーマは , 後て、ファイル名を変更 したくなったときにどうするかて、ある。た とえば , 『 32 日ステージ』という短期会議が ある。これはあるテーマに対して 32 日間て、 グラムを書けば一発て、て、きそうな気がして しまう。これを , Fig. 1 のようにダミーのフ 会議を尽くすという無謀な会議なのだが , くれば , かなりプログラマて、ある。 hex. 1 か ァイルを与えて実行した。コンパイルする 以下のような名前のファイルとする。 手間が省けるのて、 , ちょっと気分的に楽か ら hex. 01 に改名するには , 次のように関数を なという程度の差て、ある。 使うとて、きる。 hex. 1 hex. 2 hex. 3 凝った作りにしようとすれば , この処理 rename( ” hex. 1, "hex. 01") ; とりあえず , MS ー DOS の環境て、処理する はいくらて、も複雑にて、きるが , て、きるだけ そこて、 , 1 ~ 9 を 01 ~ 09 に改名するには , Li 前提にしておく。なぜ 32 日ステージが hex な 単純に , かっ危険を少なく , 短時間て、実行 st 1 のようなプログラムて、よい のか , それには深い訳があるのだが , ペー するという条件の下て、は , プログラムを使 List 1 程度なら , ちょっと C をかじった程 ジが足りないのて、今回はそういうものだと ってバッチファイルを作ってしまうという 度の人ならすぐ書けるはすだ。皆さんなら 思っていただきたい。これらを次のような 方法も一案て、ある。とくに , 作成したバッ どう書くだろうか。実は , 私はこのように 名前にリネームしたい チファイルを確認すれば , 危険を回避する hex. 02 hex. 03 書かなかった。 List 2 のように書いたのだ hex. 01 効果がある点にも注目してよし 、。ノヾッチフ つまり , 1 ~ 9 という数字に対するファイ このプログラム自体は恐ろしく単純だ。 for ァイルの内容は , ル名を , 01 ~ 09 のように 2 桁にしたいのだ。 と printf 程度しか知らなくても書ける。 C の ren hex. 1 hex. 01 プログラムを初めて書いたその日に作れる そんなの最初からやっておけば何もしなく ような代物て、ある。これを実行すると のような行が羅列されているだけだから , てよいのに ? そりやそうだが , それて、は ざっと眺めて問題ないことを確認すればい ren なんたら , かんたら というわけて、 , 心 原稿のネタにならない いのだ。プログラムの中て、 rename を実行し と画面に表示されるのだが , 画面に表示す 情的には , 次のようにて、きればありがたい て , うつかりバグが入り込んて、しまったと るのて、はなく , 次のようにバッチファイル ren hex. ? hex. 0? きに , 取り返しのつかない結果になりかね に書き出すのだ。 ちょっと無茶だが , 最初にマッチした文 xren > yren. bat 字がそのまま使われるという感じて、 , 意図 このリスクを回避するために , ダミーの そして , yren. bat を実行したのだ。これは二 は想像していただきたい。もちろん MS-DO ファイルを置いてテストしたり , 試作時に 度手間になってしまうが , C て、バッチファイ S て、このように指定することはて、きない。て、 ルを書こうという発想がポイントて、ある。 printf を突っ込んて、みるのもおもしろくな は , どうすればいいか。ひとつずつ ren を実 い。どうせ printf を入れるなら最初から prin 実際は C て、はなく jgawk て、バッチファイルを 行するという手はあるが , 実際にやってみ tf だけにしてしまえ , という逆転の発想から ると「なぜコンピュータという便利なものが 作った。 List 3 のようなスクリプトて、ある。 生まれたのが List 2 なのて、ある。 あるのに , こんな単調な手作業を・・・・司と , ' がないのと , i をいきなり使っているの ( フィンローダ NlFTY-Serve FPROG SYSOP) が違う程度て、 , ほとんど同じノリて、書けて こて、 , プロ かなり虚しいこと甚だしい jgawk 用のスクリプト バッチファイルを作成するためのプログラム ファイル名を変更するプログラム List int main() 3 : i nt i ; 4 : for (i i く 10 ; 5 : char 01dname[7 6 : Char newname [ 8 7 : 8 : sprintf(oldname, ” hex. %d ” 9 : sprintf(newname, ” hex. 0 % d ” 10 : rename(o ldname, newname) ; return 0 ; List List ーヨ一」ーヨョ第ョ一 4 1 一 1 ーー 1 : BEGIN { i く 10 : i + + ) { for (i 2 : printf("ren hex. %d hex. 0%d%n" 3 : 4 : 一〇 int main() 3 : 4 : 5 : for (i i く 10 : i + + ) { 6 : printf(" hex. %d hex. 0%d\n ” ren 7 : 8 : return 0 : Fig 1 ⅱ st3. awk の使用例 jgawk -f list3. awk dummy > yren. bat フィンローダのあつばれご意見番 153
特集・正規表現のすすめ・ translate メンバは , 文字列の変換を行う ときに用います。 regex. c の最後についてい るテスト用 main 関数て、は , 大文字と小文字 を区別しないマッチのために , 変換テープ ルを割り当てています。変換しないときは NULL にしておきます ( 29 行目 ) 。 こうしてコンパイルの準備は済みました。 re_compile_pattern 関数て、 , 入力されたパタ ーンをコンパイルします ( 34 行目 ) 。第 2 引数 には文字列の長さを指定します。 36 行目て、は , re compile fastmap 関数に よって , 高速検索のための表を作成してい ます。 検索する ()e search, 「 e match) これて、 , ようやく検索がて、きます。コン パイルされたパターンと , 文字列とのマッ チングを取るわけて、すが , 単純にマッチン グを取るだけの re match と , マッチングの TabIe 17 「 e set_syntax に与えるフラグ 開始位置をずらしながらマッチングを取る re search のふたつの関数があります ore m atch が strcmp, re_search カ sstrstr にヌ寸応す るといったら , ご理解いただけるて、しよう , こて、は re search 関数を用いて , 入力さ れた文字列からパターンを検索しています ( 41 行目 ) 。第 2 , 3 引数は文字列とその長さ て、す。第 4 引数はマッチングの開始位置て、 , 第 5 引数は検索範囲を示します。第 5 引数を 負の数にすると後ろ向き検索になります。 こて、は , 文字列の先頭から文字列全体に ついて検索をかけています。 最後の引数は , マッチングの詳細を得る ためのバッファて、 , オプション機能て、す。 使用しないときには NULL を渡します。 詳細情報は , マッチした文字列の開始・ 終了位置 (start [ 0 ] , end [ 0 ] ) , メタキャ ラクタの、、 , クて、囲まれた部分に対応する文字 フラグ (a)RE NO BK PARENS (b)RE NO BK VBAR (c)RE BK PLUS QM (d) RE TIGHT VBAR (e)RE NEWLINE OR (f)RE CONTEXT INDEP OPS (g)RE AWK CLASS HACK (h)RE SYNTAX POSIX EGREP (i)RE SYNTAX AWK (j) RE_SYNTAX_EGREP (k) RE SYNTAX GREP (I)RE SYNTAX EMACS 意 \ n を OR 演算子として扱う ーの結合度を気 $ より強くする \ 十 , \ ? をメタ文字にする ーをメタ文字にする ( , ) をメタ文字にする 味 , $ , * , 十 , ? , ーに文脈依存性を持たせる [ ] 内でも \ をエスケープ文字とする (a) 十 ( b ) 十 (f) 0 TabIe 18 GNU 正規表現ライプラリの関数 int re set_syntax(int flag) , 文法を設定する。 flag は TabIe 1 7 を組み合わせて用いる。 char* 「 e compile patte 「 n (char * pat, int len, struct re_pattern buffe 「 * buf) , 正規表現 pat をコシバイルする。成功すれば NULL , 工ラーのときはエラーメッセージを返す。 void re compile fastmap(struct re_pattern buffer * buf) , int 「 ange, struct re registers * ) , VOid re search (struct 「 e_pattern buffer* , cha 「 * str, 文字列 st 「とマッチを試みる。戻り値はマッチした長さ。ー 1 ならマッチ不成功 , ー 2 はエラー struct re registers * ) , VOid re match (struct re_patte 「 n buffe 「 * , char* str, re sea 「 ch で高速検索するための表をコンノヾイルする。 int len, int pos, int len, int pos, 文字列 st 「から検索を行う。戻り値はマッチした位置。ー 1 ならマッチ不成功 , ー 2 はエラー 列の開始・終了位置 ( start [ 1 ] , end [ 1 ] —s tart [RE NREGS-I] , end CRE NREGS ー 1 ] ) て、す。 このほか , 関数の戻り値として , re sear ch はパターンの見つかった位置 , re match はマッチした文字列の長さを返します。ま た , マッチングに失敗したときや , メモリ 不足なじのエラーも , 戻り値によって知る ことがて、きます。 以上に用いた関数を TabIe 18 にまとめて おきます。なお , もっと詳しい情報や仕様 を知りたいときには , ソースプログラム reg ex. c, regex. h を直接読むのがいちばんよい と思います。 コンバイル 正規表現ライプラリは regex. c, regex. h の ふたつのファイルから構成されています。 また , alloca 関数のない Turbo C, BorIan d C 十十などて、は alloca を用意する必要があ ります。 serow 氏による alloca. asm が定評が あるようて、す。これらのファイルは , 同氏 の手による jgawk などのアーカイプに収録さ れている ( 編注 : jgawk の現バージョンて、は alloca. asm は含まれなくなりました ) のて、 , それを利用てきます。 DOS 上て、コンパイルするときの注意とし ては , 定数 MSDOS や , 必要に応じて KAN JI を定義すること , スタックエリアを十分に 取っておくことなどがあげられます。 Turb 0 C, BorIand C 十十て、は , stklen 変数に 特集正規表現のすすめ 65 心ながら注意を喚起しておきます。 なら , もう大丈夫だとは思いますが , 老婆 S のパスの区切り記号て、悩んだ経験のある方 として扱ってくれません。これは , MS-DO 、、 \ \ 〃にしておかないと , C コンパイラが、、 \ 〃 ゞ \ 〃文字はふたつ重ねておくようにします。 に文字列として埋め込んて用いる場合には , なお , 正規表現のパターンを , C のソース コマンドによって指定します。 S ー C ては , / F オプションやリンカの /STACK よってスタックの大きさを指定します。 M
特集・正規表現のすすめ① 正規文法というのは , 文法℃ = ( T , N,P, S) クにおいて , 集合 P のすべての生成規則 が , 、、 A → a クあるいは、、 A → bB 〃の形式てある ものをいいます。ただし , A, B は非終端記 号すなわち N の要素 , a は終端記号すなわち T の要素 , または空列ど , b は終端記号・・・・・・と いうようなムッカシイことは , よっぱど暇 なときに考えることにして , 通常 , 正規表 現というと ,Table 1 の記号と普通の文字と によって示されるパターンのことをいいま す。 例えば , Fumi と fumi とのふたつにマッチ するパターンは , 、、 [ Ff ] umi 〃と書けますし , 数字列は , 0 ー 9 ] 十〃と書くことがて、きま す。 TabIe 2 に簡単な例を示しておきます。 これをみながら , メタ文字がどんな働きを しているのかを理解してみて下さい つまり , 文字や繰り返し記号などを組み 合わせて , なんらかのパターンを記述しよ うというものて、す。このように , シンプル な記号列によって , 複雑な何通りもの文字 列を表すことがて、きるのが正規表現の利点 て、す。 TabIe 3 は , しばしば用いられるパターン を集めたものて、すが , これくらいになると , いくぶん込み入ってきます。て、すが , 、、 [ A ー Za ー z ] 十〃や、、 [ 0 ー 9 ] 十〃などをひとまとまりの 単語のように考えれば , わりと扱いやすく なると思います。 なお , 以上の表にあげた記法は代表的な ものて、あって , 必ずしもこうだとは限りま せん。処理系によって , カッコの前に、、 \ クを 、、 * 〃て、なく、、 @ 〃を使うと 必要とするとか , かのバリエーションがあります。あるいは , perl の正規表現のように繰り返し回数が指定 て、きるなどの機能拡張が施されているもの もあります。 ここて、少々余談て、すが , 「正規表現」とい う言葉について考えておきたいと思います。 正規表現とは , regular expression の素朴 な訳て、す。岩谷宏氏は℃の宝箱』 ( アレン・ ホラブ著 , 工学社 ) の翻訳において , この言 葉に「定型式」という訳語をあてておられま す。つまり ,expression を , プログラミング における数式の式クと解釈するわけて、す。 このことから示唆を受けるのて、すが , ちょ うど数式て、、、 ( ( 1 < < 2 ) 十 a) 〃と書いて , ある 数値を示すように , 正規表現とは、、 [ A ー Z ] Ca-z] * 〃と書いて , ある文字列を示している , そういうものなんだな , と理解て、きます。 regular の解釈については , 正規文法の性 質と , 文脈自由文法 , 文脈依存文法などの 他の文法との関係から考えると , く規則的 な , 規則正しい , 系統立った > のニュアン Fig. 1 g 「 ep でヘッダファイルから検索する ( 下線部が入力した文字列 ) FARFUNC fputchar(int FARFUNC flushall (void) : FARFUNC fgetchar(void) : FARFUNC fcloseall (void) : _Cdecl ferror(FILE -FAR *--fp) : _Cdecl fe0f(FILE -FAR *--fp) : fsetpos(FILE -FAR *--stream, const fpos-t -FAR *--POS) : int FARFUNC fseek(FILE -FAR *--stream, long --offset, whence) : FARFUNC fscanf(FILE -FAR *--stream, const char -FAR *--format' FARFUNC fputs(const char -FAR *--s, FILE -FAR *--stream) : FARFUNC fputc(int c, FILE -FAR *--stream) ; FARFUNC fprintf(FILE -FAR *--stream, const char -FAR *--format' fgetpos(FILE -FAR *--stream, fpos-t -FAR *--POS) : FARFUNC fgetc(F ILE -FAR *--stream) : FARFUNC fflush(FILE -FAR *--stream) ; Cdecl _FARFUNC fcIose(FILE -FAR *--stream) ; A>grep "int. *[ YtlfLa-zl + (" /bc3/include/stdi0. h i n t i n t i n t int int int i n t i n t int i n t i n t i n t int i n t i n t int _Cdecl Cdecl Cdecl Cdecl Cdecl ー Cd ecl Cdecl Cdecl _Cdecl Cd ecl ー Cdecl Cdecl -Cdecl スが強いと思います。ただ「正規」というと 「規則正しい」というよりは , 「正式」という 臭いがして , はたして適訳なのかなあと思 いますが , ま , これは , 英語の強い方にお 任せしましよう。 なにはともあれ , 訳語の問題を通じて物 事の理解が深まるのはとてもいいことだと 思います。 正規表現はどこに ? 正規表現は , わりと複雑なパターンを柔 軟に表せるのて、 , 検索文字列の指定を中心 いろいろなところに使われていま す。則述の grep, sed, awk, perl といっ た , ツールやフィルタ系言語て、は , とくに 重要な役割りをはたしています。 grep, eg rep て、は , ファイルからあるパターンを含ん だ行を検索します。 sed, awk, perl は , さ らに , 検索したパターンを削除・置換した り , ある書式て、出力したり , 高度な処理を 記述するものて、す。いずれも正規表現を用 いてパターンを書きます。 Fig. 1 に BorlandC 十十のヘッダファイル から , grep によって関数プロトタイプを探 し出す例を示します。ここて、は , int 型て、頭 文字が f て、あるものを抽出していますが , C decl や FARFUNC などの修飾子を無視する * 〃というパターンを用いていま ために す。また関数名直前の空白は、、 [ \ t ] 〃によっ て , スペースかタブ記号にマッチさせてい ます。そして , f につづく小文字列贒 a ー z ] 十ク と開きカッコて、関数名を示します。こんな ふうにして , 正規表現を用いるのてす。 ⅵや emacs などのエデイタては , 編集作業 の上て , 検索・置換のパターンに正規表現 を用いることがて、きます。ⅵのように検索ハ ターンが正規表現の場合 , 検索文字列の中 に ! 〃や ] クなどのメタ文字を含むならば , メタ文字をちゃんとエスケープしなければ なりません。このことを知らないと , 探し たいパターンを正しく指定てきない 特集正規表現のすすめ 39
特集 ( ) 正規表現のすすめ① sed -n スクリプトファイル名 . sed スクリプトの各コマンドは次のような 形をしています。 のように -n オプションを指定して実行する [ アドレス , [ アドレス ] ] ファンクシ と , 原則として結果を出力しません。このと ョン [ 引数 ] きは , 出力したいときは , 明示的に表示コ . ] は ... が省略可能て、あることを表 マンドを実行させます。 表示コマンドは , します。 [ アドレス , [ アドレス ] ] [ アドレス [ , アドレス ] ] p の部分はファンクションに対応する操作を の形をしています。 スクリプトがオプションと紛らわしいな 行う範囲の指定て、す。 [ 引数 ] どのときのために , スクリプトて、あること の部分の形式はファンクションによって決 を明示的に表すための -e オプションがあり まります。 ます。 アドレスとしては , sed -e スクリプトファイル名 . ・ n 番目の入力行であることを表す のように使います。また , スクリプトがや 整数 やこしくなったときには , スクリプトを適 ( 1 から始まり , 複数のファイルから入 当なファイルに入れて , 力する場合て、も通し番号 ) sed -f スクリプトファイル名ファイル ・最後の入力行であることを表す 名 .. のように使うことがて、きます。もちろん , ・正規表現にマッチする文字列を含む行で これらと -n オプションを組み合わせて使う あることを表す こともて、きます。 / 正規表現 / sed のコマンドには実にたくさんの種類が ( 正規表現の構文は grep とほば同じ ) ありますが , 正規表現との関係ということ が使えます。 て、 , 置換コマンドを紹介します。 コマンドラインから次のようにして sed を [ アドレス [ , アドレス ] ] s / 正規表現 / 使うことがて、きます。 置換文字列 / フラグ .. sed スクリプトファイル名 . の形をしています。これが実行されると , とすると , sed は各ファイルから順に 1 行ず その行の中て、正規表現にマッチする文字列 つ入力します。ファイル名を省略すると標 を探して , 見つかれば置換文字列に置き換 sed 準入力から入力します。出力は標準出力に えます。マッチする文字列が行の中に複数 sed のコマンドラインからの使用の基本は 行います。 ある場合 , 特に指定しなければ , 置換され 各行を入力するたびに , スクリプトの各 次のとおりて、す。 るのはマッチした文字列て、行の中て、一番左 sed スクリプトファイル名 .. コマンドごとに現在の行が [ アドレス [ , ア にあるむのだけて、すが , フラグとして g を指 定するしマッチした文字列すべてを置換 ドレス ] ] て、指定される範囲に入っているか スクリプトというのが sed に与えるコマンド を書き並べたものて、す。スクリプトがオプ どうかを調べて , 入っていればファンクシ します。また , フラグとして p を指定する ョンに対応する操作を実行します。そして , ションと紛らわしいときには , と , 置換が起きたときに結果が出力される sed -e スクリプトファイル名 . 原則として結果を出力します。ただし , 削 ようになります。 sed を一 n オプションて、起動 除コマンドが実行された場合はその行は出 したときに p フラグは便利て、す。 とします。また , スクリプトがややこしく 実は grep ててきることはすべて sed てもて、 なったときには , 適当なファイルに書き込 力されません。 削除コマンドは , んておいて , きます。たとえば , sed -f スクリプトファイル名ファイル [ アドレス [ , アドレス ] ] d grep ab*c filel 名 .. の形をしています。 として呼び出すことがてきます。 それに対して , て、もて、きます。 例 10 長さが偶数である行だけを出力する egrep のほうが便利て、す。前章て、解説した 正規表現 ( い , ) * ) を応用して , egrep (). ) * $ file て、て、きます。これも行の先頭を表すと行の 末尾を表す $ が重要て、す。 例 11 ちょっと変な例 grep egrep はどうなるて、しよう。どの行にも行の先頭 というものはありますから , 正規表現はす べての行にマッチします。したがって , い すれも , cat file と同じことになります。 例 12 同じ文字列の 3 回の繰り返しからなる 行だけ出力する これは egrep て、はて、きません。 grep を使え ば , grep \ (. * \ ) \ 1 \ 1 $ file て、て、きます。 sed と awk は , sed -n /ab*c/p filel 特集 正規表現のすすめ 53